Difference between revisions of "Splatoon 2/Save"
(Add encoding info) |
(Add missing base info) |
||
Line 14: | Line 14: | ||
! Ver | ! Ver | ||
! Added On | ! Added On | ||
+ | ! Save Body Size | ||
! Description | ! Description | ||
|- | |- | ||
| 0 | | 0 | ||
| 1.0.0 | | 1.0.0 | ||
+ | | 0x483B0 | ||
| Original shipped version, saves in plain text. | | Original shipped version, saves in plain text. | ||
|- | |- | ||
| 1 | | 1 | ||
| 1.1.0 | | 1.1.0 | ||
+ | | 0x483E0 | ||
| Introduced encryption. Added in day 1 patch. | | Introduced encryption. Added in day 1 patch. | ||
|- | |- | ||
| 2 | | 2 | ||
− | | | + | | 2.3 |
+ | | 0x86840 | ||
| Added Stats Data (SaveDataStats) | | Added Stats Data (SaveDataStats) | ||
|- | |- | ||
| 3 | | 3 | ||
| 3.0.0 | | 3.0.0 | ||
+ | | 0x88D90 | ||
| Added Octo Expansion Data (SaveDataMsnOcta) | | Added Octo Expansion Data (SaveDataMsnOcta) | ||
|- | |- | ||
| 4 | | 4 | ||
| 4.0.0 | | 4.0.0 | ||
+ | | 0x88D90 | ||
| Introduced save body shuffle. Content is the same as Ver 3 | | Introduced save body shuffle. Content is the same as Ver 3 | ||
|} | |} |
Revision as of 04:07, 27 October 2018
Splatoon 2 saves its data in save.dat
.
Tools
- SplatHeX: a full fledged save editor for Windows.
- Splatsave: an open source program that decrypts and re-encrypts
save.dat
.
Revisions
Since release, there has been 5 revisions to this file:
Ver | Added On | Save Body Size | Description |
---|---|---|---|
0 | 1.0.0 | 0x483B0 | Original shipped version, saves in plain text. |
1 | 1.1.0 | 0x483E0 | Introduced encryption. Added in day 1 patch. |
2 | 2.3 | 0x86840 | Added Stats Data (SaveDataStats) |
3 | 3.0.0 | 0x88D90 | Added Octo Expansion Data (SaveDataMsnOcta) |
4 | 4.0.0 | 0x88D90 | Introduced save body shuffle. Content is the same as Ver 3 |
Encoding
The encoding process takes place in Cmn::SaveDataFactory::encode
.
The decoding process takes place in Cmn::SaveDataFactory::decode
.
The encryption used on the save body is AES-128 CBC. The key is generated
using a sead::Random
object initialized from a 16 byte seed stored
in the save footer, in which also stores the initialization vector and the CMAC
for authentication. The CMAC key is generated from the same
sead::Random
object after the encryption key.
The shuffling for save body takes place after it is encrypted. The game
constructs a new sead::Random
object initialized with the CRC32
from the save header. It then uses this to generate block sizes larger than
1/16 of body size and smaller than 1/8 of body size (or total remaining size,
in case the remaining size is smaller than 1/8 of body size). When the
remaining size is smaller than 1/16 of body size, it is added as the size of
the last block. The game then uses sead::PtrArrayImpl::shuffle
with the same sead::Random
object to complete the shuffle.
save.dat file structure
Save Header:
Offset | Size | Description | Value |
---|---|---|---|
0x0 | 4 | (Save Version) | 0, 1, 2, 3 or 4, etc |
0x4 | 4 | (Dev Save Version?) | |
0x8 | 4 | crc32 of save body (everything between header and footer) | DEADBEAF |
0xC | 4 | (Padding) | 0 |
Save Body Structure
Offset | Absolute Offset | Size | Description |
---|---|---|---|
0x0 | 0x10 | 0x2B210 | SaveDataCmn |
0x2B210 | 0x2B220 | 0xC750 | SaveDataVss |
0x37960 | 0x37970 | 0x33E8 | SaveDataLocal |
0x3AD48 | 0x3AD58 | 0x5E78 | SaveDataMsn |
0x40BC0 | 0x40BD0 | 0x2D00 | SaveDataShop |
0x438C0 | 0x438D0 | 0x49C0 | SaveDataCoop |
0x48280 | 0x48290 | 0x11C | SaveDataFest |
0x4839C | 0x483AC | 0x3E460 | SaveDataStats |
0x867FC | 0x8680C | SaveDataMsnOcta |
SaveDataCmn(0x10):
Offset | Absolute Offset | Size | Description | Value |
---|---|---|---|---|
0x0 | 0x10 | 4 | PlayerModelType (Gender, basically) | 0: Woomy | 1: Ngyes | 2: Veemo | 3: NO, 2 is better |
0x4 | 0x14 | 4 | PlayerHairId | See Mush/HairInfo.byml |
0x8 | 0x18 | 4 | PlayerBottomId | See Mush/BottomInfo.byml |
0xC | 0x1C | 4 | PlayerSkinColorId | 0 to 6, 0 being lightest and 6 darkest |
0x10 | 0x20 | 4 | PlayerEyeColorId | 0 to 13, each value represents a color |
0x14 | 0x24 | 4 | Weapon (Equipped Main Weapon Id) | See Mush/WeaponInfo_Main.byml |
0x18 | 0x28 | 4 | Weapon (Equipped Sub Weapon Id) | See Mush/WeaponInfo_Sub.byml |
0x1C | 0x2C | 4 | Weapon (Equipped Special Weapon Id) | See Mush/WeaponInfo_Special.byml |
0x20 | 0x30 | 4 | Weapon (Equipped Weapon Turf inked) | 1234567 |
0x24 | 0x34 | 4 | unk (padding?) | 0? |
Weapon Inventory part:
Offset | Absolute Offset | Size of Each Entry (HaveWeapon) | Total Size | Description |
---|---|---|---|---|
0x28 | 0x38 | 0x130 | 0x13000 | (Weapon Inventory) |
Structure of Entry (HaveWeapon)
Offset | Size | Description | Value |
---|---|---|---|
0x0 | 4 | Weapon (WeaponMain id) | See Mush/WeaponInfo_Main.byml |
0x4 | 4 | Weapon (WeaponSub id) | See Mush/WeaponInfo_Sub.byml |
0x8 | 4 | Weapon (WeaponSp id) | See Mush/WeaponInfo_Special.byml |
0xC | 4 | Weapon (Turf inked) | 1234567 |
0x10 | 0x8 | (Time. Last used time?) | UNIX Epoch time |
0x18 | 0x4 | unk | |
0x1C | 0x4 | unk | |
0x20 | 0x108 | unk (Some weird block, likely represents various flags) | A buncha 0x55555555, bit flipped to 0xAAAAAAAA at some places |
0x128 | 4 | (New or not) | 0 = new, 1 = not new |
0x12C | 4 | unk (padding?) | 0? |
SaveDataCmn (Cont'd):
Offset | Absolute Offset | Size | Description | Value |
---|---|---|---|---|
0x13028 | 0x13038 | 4 | (EquippedShoeId) | See Mush/GearInfo_Shoes.byml |
0x1302C | 0x1303C | 4 | (EquippedClothesId) | See Mush/GearInfo_Clothes.byml |
0x13030 | 0x13040 | 4 | (EquippedHeadGearId) | See Mush/GearInfo_Head.byml |
0x13034 | 0x13044 | 4 | unk (padding?) | 0? |
Gear Inventory part:
Offset | Absolute Offset | Size of Each Entry (HaveGear) | Total Size | Description |
---|---|---|---|---|
0x13038 | 0x13048 | 0x30 | 0x6000 | (Shoe Inventory) |
0x19038 | 0x19048 | 0x30 | 0x6000 | (Clothes Inventory) |
0x1F038 | 0x1F048 | 0x30 | 0x6000 | (HeadGearInventory) |
Structure of Entry (HaveGear):
Offset | Size | Description | Value |
---|---|---|---|
0x0 | 4 | (Gear id) | See GearInfo_XXXX.byml |
0x4 | 4 | (Total # of unlocked abilities - including main slot) | 1, 2, 3, or 4 |
0x8 | 4 | (Total # of ability slots - including main slot) | 1, 2, 3, or 4 |
0xC | 4 | (Main slot ability id) | To be filled in |
0x10 | 4 | (Sub slot#1 ability id) | To be filled in |
0x14 | 4 | (Sub slot#1 ability id) | To be filled in |
0x18 | 4 | (Sub slot#1 ability id) | To be filled in |
0x1C | 4 | unk | |
0x20 | 0x8 | Time (Last used time?) | UNIX Epoch time |
0x28 | 4 | (New or not) | 0 = new, 1 = not new |
0x2C | 4 | unk (padding?) | 0? |
SkillChip (Ability Chuncks) Inventory part:
Offset | Absolute Offset | Size of Each Entry (HaveSkillChip) | Total Size | Description |
---|---|---|---|---|
0x25038 | 0x25048 | 0x8 | 0x200 | (SkillChip (a.k.a. Ability Chuncks) Inventory) |
Structure of Entry (HaveSkillChip):
Offset | Size | Description | Value |
---|---|---|---|
0x0 | 4 | (SkillId?) | To be filled in |
0x4 | 4 | (Skill chip amount) | 696 |
BoostTicket (Crusty Sean) Inventory part:
Offset | Absolute Offset | Size of Each Entry (HaveBoostTicket) | Total Size | Description |
---|---|---|---|---|
0x25238 | 0x25248 | 0xC | 0x300 | (BoostTicket Inventory) |
Structure of Entry (HaveBoostTicket):
Offset | Size | Description | Value |
---|---|---|---|
0x0 | 4 | (Ticket Id) (for what kind of food or drink) | To be filled in |
0x4 | 4 | (Boosted skill id for drinks) (-1 for food tickets) | To be filled in |
0xC | 4 | (Ticket amount) | 69 |
SaveDataCmn (Cont'd):
Offset | Absolute Offset | Size | Description | Value |
---|---|---|---|---|
0x25538 | 0x25548 | 4 | TutorialResult | 0 for not seen tutorial, > 0 for seen tutorial? |
0x2553C | 0x2554C | 0x4 | CtrlOption (Handheld Stick) | Float, 1.0 for +5, -1.0 for -5 |
0x25540 | 0x25550 | 0x4 | CtrlOption (Handheld Motion) | Float, 1.0 for +5, -1.0 for -5 |
0x25544 | 0x25554 | 0x4 | CtrlOption (Handheld motion ctrl flag) | 1 for enabled |
0x25548 | 0x25558 | 0x40 | CtrlOption (unk) | A buncha 0s? |
0x25588 | 0x25598 | 0x4 | CtrlOption (TV/Tabletop Stick) | Float, 1.0 for +5, -1.0 for -5 |
0x2558C | 0x2559C | 0x4 | CtrlOption (TV/Tabletop Motion) | Float, 1.0 for +5, -1.0 for -5 |
0x25590 | 0x255A0 | 0x4 | CtrlOption (TV/Tabletop motion ctrl flag) | 1 for enabled |
0x25594 | 0x255A4 | 0x40 | CtrlOption (unk) | A buncha 0s? |
0x255D4 | 0x255E4 | 0x10 | OnlinePlayPenalty (4 values) | Haven't looked into yet |
0x255E8 | 0x255F8 | 0x8 | OnlinePlayPenalty Time (penalty start time?) | UNIX Epoch time |
0x255F0 | 0x25600 | 0x188 | unk (Haven't looked into yet) | |
0x25778 | 0x25788 | 4 | HaveMoney | 9696969 |
0x2577C | 0x2578C | 4 | HaveTurbanShell (Sea snails) | 969 |
0x25780 | 0x25790 | 0x108 | unk (Haven't looked into yet) | |
0x25888 | 0x25898 | 8 | Time (time of save?) | UNIX Epoch time |
0x25890 | 0x258A0 | 0x4458 | unk (Haven't looked into yet) | |
0x29CE8 | 0x29CF8 | 0x1528 | unk (Padding?) | A buncha 0? |
SaveDataVss(0x2B220):
Offset | Absolute Offset | Size | Description | Value |
---|---|---|---|---|
0x0 | 0x2B220 | 4 | PlayerRank (Not rank, but level. They call it rank in the code.) | Looks like level 1 is `0` and level 2 is `1`, etc) |
0x4 | 0x2B224 | 4 | Experience | To be filled in |
0x8 | 0x2B228 | 4 | StarRank | To be filled in |
0x14 | 0x2B234 | 4 | (TicketBoostRemaining) | 20 |
0x8768 | 0x33988 | 4 | (X Power Rainmaker) | 2770.2 (Float) |
0x87B0 | 0x339D0 | 4 | (X Power Splatzone) | 2809.7 (Float) |
0x87F8 | 0x33A18 | 4 | (X Power Tower Control) | 2801.4 (Float) |
0x8840 | 0x33A60 | 4 | (X Power Clam Blitz) | 2797.1 (Float) |
SaveDataLocal(0x37970):
SaveDataMsn(0x3AD58):
Offset | Absolute Offset | Size | Description | Value |
---|---|---|---|---|
0x1E6C | 0x3CBC4 | 4 | (HeroModePowerOrb) | 6969 |
0x1E70 | 0x3CBC8 | 4 | (MechanicalFish) | 69 |
SaveDataShop(0x40BD0):
SaveDataCoop(0x438D0?):
More to be added here...
Save Footer:
Used for save crypto (as of ver 4)
Offset | Size | Description |
---|---|---|
EOF-0x30 | 0x10 | IV (initialization vector) |
EOF-0x20 | 0x10 | Key Seed |
EOF-0x10 | 0x10 | AES-CMAC |