Closed TristanWiley closed 9 months ago
Palworld wraps their save file with compression as you said. The format is:
4 bytes - uncompressed length
4 bytes - compressed length
3 bytes - magic bytes PlZ
1 byte - save type. valid options: 0x30, 0x31, 0x32
For save type 0x31
, it is compressed with zlib once. For save type 0x32
, it is compressed with zlib twice, and the lengths refer to the data after first decompression.
There is however issues with loading Level.sav
, all other save files parse correctly.
$ uesave to-json --input Level.sav.gvas
offset 1773: StructType for ".worldSaveData.CharacterSaveParameterMap.Key" unspecified, assuming Guid
offset 1773: StructType for ".worldSaveData.CharacterSaveParameterMap.Value" unspecified, assuming Struct(None)
Error: at offset 3461993: io error: failed to fill whole buffer
Here's a sample of uncompressed save files: PalWorldUncompressedSave.zip
This looks like it's just bad inferred types.
uesave
works fine (for converting to JSON, still working on the inverse process to load into the game) with the following types (the issue mainly stems from Key
s being inferred as Guid
s, but IDs in PalWorld tend to be structs)
uesave to-json --input Level.sav.gvas \
--type .worldSaveData.CharacterSaveParameterMap.Key=Struct \
--type .worldSaveData.FoliageGridSaveDataMap.Key=Struct \
--type .worldSaveData.FoliageGridSaveDataMap.ModelMap.InstanceDataMap.Key=Struct \
--type .worldSaveData.MapObjectSpawnerInStageSaveData.Key=Struct \
--type .worldSaveData.ItemContainerSaveData.Key=Struct \
--type .worldSaveData.CharacterContainerSaveData.Key=Struct
That makes sense! Might be a silly question, how did you go about decompressing the save files?
That makes sense! Might be a silly question, how did you go about decompressing the save files?
https://gist.github.com/cheahjs/300239464dd84fe6902893b6b9250fd0
That worked! Thanks so much! Let me know if you happen to figure out the inverse process oops didn't see the second file. I really appreciate the help
One more unrelated question, any idea what the file name structure is? I assume it relates to a Steam ID of some sort but I've spent hours trying to decipher it and how it's encoded and I can't figure it out. I know the 0000000000... file is for a user that creates the world. But the other ones are definitely related to some unique identifier for the user.
One more unrelated question, any idea what the file name structure is? I assume it relates to a Steam ID of some sort but I've spent hours trying to decipher it and how it's encoded and I can't figure it out. I know the 0000000000... file is for a user that creates the world. But the other ones are definitely related to some unique identifier for the user.
Not sure either. I've been trying to figure it out so I can convert my dedicated server player.savs to another machine since those IDs are per-machine as well. So I can't just copy paste my Player/*.savs
The filename does match the first 32 bits of IndividualId and PlayerUId in the GVAS/json data.
EDIT: The reason my UIDs were different between hosts is because the dedi I had symlinked the steamclient.so to the palworld's linux64/steamclient.so and on my desktop it was using my Linux client's steam install steamclient.so. I guess the steamclient.so managed by steam is newer than the one shipped with palworld and generates that UID based on the steamid differently? Regardless symlinking to the palworld shipped one does fix my UIDs being different. And for anyone trying to convert a localhost save to a dedicated save it looks like someone made a script (based on cheahjs' code) https://github.com/xNul/palworld-host-save-fix to convert the 00..001 GUID to a proper GUID.
One more unrelated question, any idea what the file name structure is? I assume it relates to a Steam ID of some sort but I've spent hours trying to decipher it and how it's encoded and I can't figure it out. I know the 0000000000... file is for a user that creates the world. But the other ones are definitely related to some unique identifier for the user.
I've did some research and basically your Player.sav file has GUID's of your container (inventory) and other stuff. These GUID's then used inside Level.sav itself where it contains data about items / slots / item amounts inside these container GUID's
Example:
^My character CommonContainerId
After looking into Level.json you can search for this GUID and look at every item attached
I think same logic applied to every other aspect of game and its containers and data holders. Basically your character is just a pointer-holder object that 100% depends on server
Hello.
I am trying to fix the BaseCampWorkerMax limit in Palworld, on my dedicated server. I have uesave but it does nothing. Is there a way to copy a file from a solo world that was generated with the limit increased? I understand the dedicated server files are a bit problematic but several sources online seem to think the process should work but I have not had that experience.
Let me know if you know anything I can do to fix this. Thanks.
This ByteProperty seems to be breaking the deserialization. It could be possible the same problem of #24
EDIT: I have managed to parse most of the Level.sav. It's not an issue with uesave-rs, just an internal structure of Palworld game.
Yeah, this works now. https://github.com/DKingAlpha/palworld-uesave-rs
Converting back and forth, only floats differ. Bytes-accurate for other data.
Excellent work! Gonna close this now as it's out of scope for this repo.
Hi! I saw some other GH issues here with various game incompatibilities/requests and figured I'd make one as well.
Looks like Palworld must have some compression that's causing it to not work with the tool. I get the following error:
Here's a couple sample player save files: PalworldSaveFiles.zip
Any help would be appreciated!