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 6 revisions to this file:
Version
|
Added On
|
Save 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.0
|
0x86840
|
Added Stats Data (SaveDataStats)
|
3
|
3.0.0
|
0x88D90
|
Added Octo Expansion Data (SaveDataMsnOcta). Introduced new key look-up table.
|
4
|
4.0.0
|
0x88D90
|
Introduced save body shuffle. Introduced new key look-up table. Content is the same as Ver 3.
|
5
|
4.2.0
|
0x88D90
|
Same look-up table as Ver 4, but removed shuffling. Content is still 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 key is generated
by Cmn::SaveDataFactory::Impl::IEncodedAllAccessor::createKey
.
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.
File Structure
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
|
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
|
0x2544
|
SaveDataMsnOcta
|
SaveDataCmn
Offset
|
Absolute Offset
|
Size
|
Description
|
Value
|
0x0
|
0x10
|
4
|
Inkling PlayerModelType (Gender, basically)
|
0: Woomy | 1: Ngyes | 6: Story Octoling (Rev 0 and 1 only)
|
0x4
|
0x14
|
4
|
Inkling PlayerHairId
|
See Mush/HairInfo.byml
|
0x8
|
0x18
|
4
|
Inkling PlayerBottomId
|
See Mush/BottomInfo.byml
|
0xC
|
0x1C
|
4
|
Inkling PlayerSkinColorId
|
0 to 6, 0 being lightest and 6 darkest
|
0x10
|
0x20
|
4
|
Inkling 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
|
Last Time Equipped
|
UNIX Epoch time
|
0x18
|
0x4
|
Freshness Level
|
3
|
0x1C
|
0x4
|
Flag Color
|
|
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
|
Equipped Shoe Id
|
See Mush/GearInfo_Shoes.byml
|
0x1302C
|
0x1303C
|
4
|
Equipped Clothes Id
|
See Mush/GearInfo_Clothes.byml
|
0x13030
|
0x13040
|
4
|
Equipped HeadGear Id
|
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
|
Gear Level (Total # of unlocked abilities - including main slot)
|
1, 2, 3, or 4
|
0x8
|
4
|
Gear Slots Unlocked (Total # of ability slots - including main slot)
|
1, 2, 3, or 4
|
0xC
|
4
|
Main slot ability id
|
See SkillInfo.byml
|
0x10
|
4
|
Sub slot#1 ability id
|
See SkillInfo.byml
|
0x14
|
4
|
Sub slot#2 ability id
|
See SkillInfo.byml
|
0x18
|
4
|
Sub slot#3 ability id
|
See SkillInfo.byml
|
0x1C
|
4
|
Current Experience
|
Value between 0 and [maxexp -1]
|
0x20
|
0x8
|
Last Time Equipped
|
UNIX Epoch time
|
0x28
|
4
|
(New or not)
|
0 = new, 1 = not new
|
0x2C
|
4
|
unk (padding?)
|
0?
|
SkillChip (Ability Chunks) Inventory part:
Offset
|
Absolute Offset
|
Size of Each Entry (HaveSkillChip)
|
Total Size
|
Description
|
0x25038
|
0x25048
|
0x8
|
0x200
|
(SkillChip (a.k.a. Ability Chunks) Inventory)
|
Structure of Entry (HaveSkillChip):
Offset
|
Size
|
Description
|
Value
|
0x0
|
4
|
Skill ID
|
See SkillInfo.byml
|
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
|
Type ID
|
0 = +50% Exp, 1 = +100% Exp, 2 = +50% Money, 3 = +100% Money, 4 = Skill Drink, 5 = +150% Exp, 6 = +150% Money
|
0x4
|
4
|
Boosted skill id for drinks (-1 for food tickets)
|
See SkillInfo.byml
|
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
|
0x1
|
CtrlOption (Handheld motion ctrl flag)
|
1 for enabled
|
0x25545
|
0x25555
|
0x1
|
CtrlOption (Handheld motion invert Y-axis)
|
1 for enabled
|
0x25546
|
0x25556
|
0x1
|
CtrlOption (Handheld motion invert X-axis)
|
1 for enabled
|
0x25547
|
0x25557
|
0x41
|
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
|
0x1
|
CtrlOption (TV/Tabletop motion ctrl flag)
|
1 for enabled
|
0x25591
|
0x255A1
|
0x1
|
CtrlOption (TV/Tabletop invert Y-axis)
|
1 for enabled
|
0x25592
|
0x255A2
|
0x1
|
CtrlOption (TV/Tabletop invert X-axis)
|
1 for enabled
|
0x25593
|
0x255A3
|
0x41
|
CtrlOption (unk)
|
A buncha 0s?
|
0x255D4
|
0x255E4
|
0x10
|
OnlinePlayPenalty (4 values)
|
Haven't looked into yet
|
0x255E8
|
0x255F8
|
0x8
|
OnlinePlayPenalty Time (penalty end 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
|
0x54
|
unk (Haven't looked into yet)
|
|
0x258E4
|
0x258F4
|
0x1
|
Color Lock
|
1 for enabled
|
0x258E5
|
0x258F5
|
0x1
|
Coconut Post Display
|
1 for enabled
|
0x258E6
|
0x258F6
|
0x2BEA
|
unk (Haven't looked into yet)
|
0x284D0
|
0x284E0
|
4
|
Octoling PlayerModelType (Gender, basically)
|
2: Female Veemo| 3: Male Veemo
|
0x284D4
|
0x284E4
|
4
|
Octoling PlayerHairId
|
See Mush/HairInfo.byml
|
0x284D8
|
0x284E8
|
4
|
Octoling PlayerBottomId
|
See Mush/BottomInfo.byml
|
0x284DC
|
0x284EC
|
4
|
Octoling PlayerSkinColorId
|
0 to 6, 0 being lightest and 6 darkest
|
0x284E0
|
0x284F0
|
4
|
Octoling PlayerEyeColorId
|
0 to 13, each value represents a color
|
0x284E4
|
0x284F4
|
4
|
Is Octoling
|
1 if yes
|
0x284F8
|
0x284F8
|
0x2D2C
|
unk (Haven't looked into yet)
|
SaveDataVss
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
|
0x18 |
0x2B238 |
4 |
Rainmaker rank |
0 = C-, 1 = C, 2 = C+, etc
|
0x24 |
0x2B244 |
4 |
Splat Zones rank |
See above
|
0x30 |
0x2B250 |
4 |
Tower Control rank |
See above
|
0x3C |
0x2B25C |
4 |
Clam Blitz rank |
See above
|
0x60 |
0x2B280 |
4 |
Turf War MMR/Power |
1969.6 (Float)
|
0x64 |
0x2B284 |
4 |
Turf War Glicko2 rating deviation |
69.6 (Float)
|
0x68 |
0x2B288 |
4 |
Turf War Glicko2 volatility |
0.06 (Float)
|
0x6C |
0x2B28C |
4 |
Turf War calculation state (?) |
1
|
0x70 |
0x2B290 |
4 |
Rainmaker MMR/Power |
1969.6 (Float)
|
0x74 |
0x2B294 |
4 |
Rainmaker Glicko2 rating deviation |
69.6 (Float)
|
0x78 |
0x2B298 |
4 |
Rainmaker Glicko2 volatility |
0.06 (Float)
|
0x7C |
0x2B29C |
4 |
Rainmaker calculation state (?) |
1
|
0x80 |
0x2B2A0 |
4 |
Splat Zones MMR/Power |
1969.6 (Float)
|
0x84 |
0x2B2A4 |
4 |
Splat Zones Glicko2 rating deviation |
69.6 (Float)
|
0x88 |
0x2B2A8 |
4 |
Splat Zones Glicko2 volatility |
0.06 (Float)
|
0x8C |
0x2B2AC |
4 |
Splat Zones calculation state (?) |
1
|
0x90 |
0x2B2B0 |
4 |
Tower Control MMR/Power |
1969.6 (Float)
|
0x94 |
0x2B2B4 |
4 |
Tower Control Glicko2 rating deviation |
69.6 (Float)
|
0x98 |
0x2B2B8 |
4 |
Tower Control Glicko2 volatility |
0.06 (Float)
|
0x9C |
0x2B2BC |
4 |
Tower Control calculation state (?) |
1
|
0xA0 |
0x2B2C0 |
4 |
Clam Blitz MMR/Power |
1969.6 (Float)
|
0xA4 |
0x2B2C4 |
4 |
Clam Blitz Glicko2 rating deviation |
69.6 (Float)
|
0xA8 |
0x2B2C8 |
4 |
Clam Blitz Glicko2 volatility |
0.06 (Float)
|
0xAC |
0x2B2CC |
4 |
Tower Control calculation state (?) |
1
|
0x260 |
0x2B480 |
0x2800 |
League Battle results |
TagScore Array (32)
|
0x8768 |
0x33988 |
4 |
Rainmaker X Power |
2270.2 (Float)
|
0x87B0 |
0x339D0 |
4 |
Splat Zones X Power |
2309.7 (Float)
|
0x87F8 |
0x33A18 |
4 |
Tower Control X Power |
2301.4 (Float)
|
0x8840 |
0x33A60 |
4 |
Clam Blitz X Power |
2297.1 (Float)
|
SaveDataLocal
SaveDataMsn
Offset
|
Absolute Offset
|
Size
|
Description
|
Value
|
0x1E6C
|
0x3CBC4
|
4
|
(HeroModePowerOrb)
|
6969
|
0x1E70
|
0x3CBC8
|
4
|
(MechanicalFish)
|
69
|
SaveDataShop
SaveDataCoop
SaveDataFest
SaveDataStats
SaveDataMsnOcta
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
|