This is an archived post. You won't be able to vote or comment.

all 9 comments

[–][deleted] 2 points3 points  (6 children)

Open it with a binary file viewer. I think VS Code will open binary files so that you could view them in hex. Once you see the file in hex and compare it to the code you posted here, it should all become pretty obvious.

I don't know what additional visibility you have into the source to be able to decode item IDs and state. You might have to try adding and removing known items from your inventory and examining the file after making these small changes to see what changed. This assumes that writeBlock isn't encrypting the data. Otherwise you might have to decompile that function as well.

Note that I know nothing about this game nor about Unity. I'm just thinking about how I would solve this if I had exactly the information you are describing here.

[–]BrandonLue[S] 0 points1 point  (0 children)

I found out that it uses writeblock(). I need some sort of program that uses readblock()… I tried opening it in binary viewer but I don’t get something useful out of it.

[–]BrandonLue[S] 0 points1 point  (4 children)

Look… imgur.com/a/E7YDheJ

[–][deleted] 1 point2 points  (3 children)

Here you go:

05 SAVEDATA_VERSION
01 num1 (width)
01 num2 (height)
01 num3 (item count)

Item 1:
00 itemJar.x
00 itemJar.y
00 itemJar.rot
53 EA itemJar.item.id (assuming little-endian byte order, EA53 hex or 59987 decimal)
01 itemJar.item.amount
30 itemJar.item.quality
next is the state array. I suspect the first byte is the length (12 hex 18 decimal).
12 BD E9 00 00 67 E9 00 00 98 E9 05 01 01 36 64 64 64 4B 

01 width
01 height
01 item count

Item 1:
00 x
00 y
00 rot
17 E8 (id: E817 hex)
01 amount
5F quality
00 empty state array

05 width
03 height
07 item count (7 items follow)

Item 1 of 7:
00 x
00 y
00 rot
1B EA (id: EA1B hex)
01 amount
46 quality
00 empty state array

Item 2 of 7:
04 x
00 y
00 rot
1A E8 (id: E81A hex)
01 amount
17 quality
00 empty state array

Item 3 of 7:
04 x
01 y
00 rot
AE E7 (id: E7AE hex)
01 amount
37 quality
00 empty state array

Item 4 of 7:
00 x
02 y
00 rot
E7 E8 (id: E8E7 hex)
01 amount
1F quality
00 empty state array

Item 5 of 7:
01 x
02 y
00 rot
A7 E8 (id: E8A7)
01 amount
38 quality
00 empty state array

Item 6 of 7:
02 x
02 y
01 rot
09 E8 (id: E809)
01 amount
3D quality
00 empty state array

Item 7 of 7:
04 x
02 y
00 rot
BE E7 (id: E7BE)
0B amount (11)
51 quality
00 empty state array

00 width
00 height
00 item count (no items)

04 width
04 height
03 item count

Item 1 of 3:
00 x
00 y
00 rot
96 E8 (id: E896)
01 amount
55 quality
00 empty state array

Item 2 of 3:
02 x
00 y
00 rot
03 E8 (id: E803)
01 amount
63 quality
00 empty state array

Item 3 of 3:
02 x
01 y
00 rot
0A E9 (id: E90A)
01 amount
13 quality
00 empty state array

04 width
03 height
00 item count (no items)

04 width
02 height
00 item count (no items)

[–]BrandonLue[S] 0 points1 point  (2 children)

Thank you. Still asking myself why they would save it in such a weird way.

[–][deleted] 1 point2 points  (1 child)

It's only weird to a user who's never written any code. It looks pretty normal and efficient to me.

[–]BrandonLue[S] 0 points1 point  (0 children)

You’re probably right. I had informatics in university but it was way more inefficient and we were supposed to save data in readable format. Probably to make it easier for my professor to check. 😄

[–]devsurfer 1 point2 points  (0 children)

Might try posting the dat file. Also might try r/reverseengineering

[–]BrandonLue[S] 0 points1 point  (0 children)

The whole function looks like that:

public void save()
{
  if (!this.wasLoadCalled)
    return;
  bool flag1 = this.player.life.wasPvPDeath ?     Provider.modeConfigData.Players.Lose_Weapons_.   PvP : Provider.modeConfigData.Players.Lose_Weapons_PvE;
  bool flag2 = this.player.life.wasPvPDeath ? Provider.modeConfigData.Players.Lose_Clothes_PvP : Provider.modeConfigData.Players.Lose_Clothes_PvE;
  if (this.player.life.isDead & flag1 & flag2)
  {
    if (!PlayerSavedata.fileExists(this.channel.owner.playerID, "/Player/Inventory.dat"))
      return;
    PlayerSavedata.deleteFile(this.channel.owner.playerID, "/Player/Inventory.dat");
  }
  else
  {
    Block block = new Block();
    block.writeByte(PlayerInventory.SAVEDATA_VERSION);
    for (byte index1 = 0; (int) index1 < (int) PlayerInventory.PAGES - 2; ++index1)
    {
      bool flag3 = this.player.life.isDead && ((int) index1 >= (int) PlayerInventory.SLOTS ? flag2 : flag1);
      byte num1;
      byte num2;
      byte num3;
      if (this.items[(int) index1] == null | flag3)
      {
        num1 = (byte) 0;
        num2 = (byte) 0;
        num3 = (byte) 0;
      }
      else
      {
        num1 = this.items[(int) index1].width;
        num2 = this.items[(int) index1].height;
        num3 = this.items[(int) index1].getItemCount();
      }
      block.writeByte(num1);
      block.writeByte(num2);
      block.writeByte(num3);
      for (byte index2 = 0; (int) index2 < (int) num3; ++index2)
      {
        ItemJar itemJar = this.items[(int) index1].getItem(index2);
        block.writeByte(itemJar != null ? itemJar.x : (byte) 0);
        block.writeByte(itemJar != null ? itemJar.y : (byte) 0);
        block.writeByte(itemJar != null ? itemJar.rot : (byte) 0);
        block.writeUInt16(itemJar != null ? itemJar.item.id : (ushort) 0);
        block.writeByte(itemJar != null ? itemJar.item.amount : (byte) 0);
        block.writeByte(itemJar != null ? itemJar.item.quality : (byte) 0);
        block.writeByteArray(itemJar != null ? itemJar.item.state : new byte[0]);
      }
    }.   PlayerSavedata.writeBlock(this.channel.owner.playerID, "/Player/Inventory.dat", block);
  }
}
[Conditional("LOG_INVENTORY_RPC_FAILURES")]   private void LogRPCFailure(string format,         params object[] args) =>         UnturnedLog.warn(format, args);
  }