22from json import JSONDecodeError
33from typing import Any
44
5- from unrealsdk import logging , make_struct
5+ from unrealsdk import logging
66from unrealsdk .hooks import Type
7- from unrealsdk .unreal import BoundFunction , UObject , WrappedStruct
7+ from unrealsdk .unreal import BoundFunction , UObject , WrappedArray , WrappedStruct
88
99import save_options .options
1010from mods_base import JSON , get_pc , hook
2222
2323
2424def _extract_save_data (
25- lockout_list : list [WrappedStruct ],
26- ) -> tuple [ dict [str , dict [str , JSON ]], list [ WrappedStruct ]]:
25+ lockout_list : WrappedArray [WrappedStruct ],
26+ ) -> dict [str , dict [str , JSON ]]:
2727 """
2828 Extracts custom save data from an UnloadableDlcLockoutList.
2929
3030 This function searches through the list for an entry matching the global `_PACKAGE_ID`.
3131 If found, it attempts to parse the `LockoutDefName` field as a JSON string into a dictionary.
3232 Invalid or malformed JSON will result in an empty dictionary and an error being logged.
33- Any entries not matching the `_PACKAGE_ID` are preserved and returned as the second element
34- of the tuple to avoid overwriting unrelated data.
33+ All entries matching the `_PACKAGE_ID` are removed in place.
3534
3635 Args:
3736 lockout_list: List of LockoutData structs from the character save file.
38-
3937 Returns:
40- A tuple with two elements:
41- - A dictionary of extracted save data (empty if none found or invalid)
42- - A list of LockoutData structs for any entries not associated with our custom saves.
38+ A dictionary of extracted save data (empty if not found or invalid).
4339 """
4440
4541 matching_lockout_data = next (
4642 (lockout_data for lockout_data in lockout_list if lockout_data .DlcPackageId == _PACKAGE_ID ),
4743 None ,
4844 )
4945 if not matching_lockout_data :
50- return {}, lockout_list
51-
52- # Preserve other package Ids on the off chance someone else uses this.
53- lockout_list_other = [
54- lockout_data for lockout_data in lockout_list if lockout_data .DlcPackageId != _PACKAGE_ID
55- ]
46+ return {}
5647
5748 extracted_save_data : dict [str , dict [str , JSON ]] = {}
5849 if matching_lockout_data .LockoutDefName :
@@ -70,7 +61,18 @@ def _extract_save_data(
7061 f" { matching_lockout_data .LockoutDefName } " ,
7162 )
7263 extracted_save_data = {}
73- return extracted_save_data , lockout_list_other
64+
65+ # Remove all our entries from the list
66+ # This is done in a bit of a weird, unpythonic way, to be extra safe with regards to the
67+ # structs. Structs are reference types, so removing one shifts all other references.
68+ i = 0
69+ while i < len (lockout_list ):
70+ if lockout_list [i ].DlcPackageId == _PACKAGE_ID :
71+ del lockout_list [i ]
72+ continue
73+ i += 1
74+
75+ return extracted_save_data
7476
7577
7678@hook ("WillowGame.WillowSaveGameManager:SaveGame" , immediately_enable = True )
@@ -90,7 +92,8 @@ def save_game(_1: UObject, args: WrappedStruct, _3: Any, _4: BoundFunction) -> N
9092
9193 # For saving, we'll overwrite existing mod data for enabled mods. Any disabled/uninstalled mods
9294 # will have their data left alone.
93- json_save_data , lockout_list = _extract_save_data (args .SaveGame .UnloadableDlcLockoutList )
95+ lockout_list = args .SaveGame .UnloadableDlcLockoutList
96+ json_save_data = _extract_save_data (lockout_list )
9497 for mod_id , mod_data in registered_save_options .items ():
9598 if mod_id in enabled_mods :
9699 mod_save_data = {
@@ -108,13 +111,10 @@ def save_game(_1: UObject, args: WrappedStruct, _3: Any, _4: BoundFunction) -> N
108111 logging .dev_warning (f"Data is not json encodable: { mod_save_data } " )
109112
110113 str_save_data = json .dumps (json_save_data )
111- custom_lockout = make_struct (
112- "UnloadableDlcLockoutData" ,
114+ lockout_list .emplace_struct (
113115 LockoutDefName = str_save_data ,
114116 DlcPackageId = _PACKAGE_ID ,
115117 )
116- lockout_list .append (custom_lockout )
117- args .SaveGame .UnloadableDlcLockoutList = lockout_list
118118
119119 # Reset our var tracking whether any options have changed since last save.
120120 save_options .options .any_option_changed = False
@@ -130,7 +130,7 @@ def end_load_game(_1: UObject, _2: WrappedStruct, ret: Any, _4: BoundFunction) -
130130 save_game = ret
131131 if not save_game :
132132 return
133- extracted_save_data , _ = _extract_save_data (save_game .UnloadableDlcLockoutList )
133+ extracted_save_data = _extract_save_data (save_game .UnloadableDlcLockoutList )
134134 if not extracted_save_data :
135135 return
136136
0 commit comments