Created
January 9, 2019 08:02
-
-
Save Reelix/7b1a837386dba0f9fe9a58c79b2c8591 to your computer and use it in GitHub Desktop.
C# Torchlight 1 Save Game Viewer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class Program | |
{ | |
private static void Main(string[] args) | |
{ | |
// https://code.google.com/archive/p/torchtools/wikis/TorchlightFileSpec.wiki | |
Console.Title = "Reelix's TL1 Save Game Viewer"; | |
byte[] fileBytes = File.ReadAllBytes(@"C:\Users\Reelix\AppData\Roaming\runic games\torchlight\save\0.SVT"); | |
// Remove first 4 - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
// Class Name | |
short classNameLength = GetShort(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 2); // Short | |
string className = GetString(fileBytes, classNameLength); | |
fileBytes = RemoveFirstBytes(fileBytes, classNameLength * 2); // UTF-16LE - Spare 00 after each byte | |
Console.WriteLine("Class Name: " + className); | |
// Difficulty | |
int difficulty = int.Parse(fileBytes[0].ToString()); | |
fileBytes = RemoveFirstByte(fileBytes); | |
Console.Write("Difficulty: "); | |
switch (difficulty) | |
{ | |
case 0: Console.WriteLine("Easy"); break; | |
case 1: Console.WriteLine("Normal"); break; | |
case 2: Console.WriteLine("Hard"); break; | |
case 3: Console.WriteLine("Very Hard"); break; | |
} | |
// Remove 3 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 3); | |
// Hardcore | |
int isHardcore = fileBytes[0]; | |
fileBytes = RemoveFirstByte(fileBytes); | |
Console.WriteLine("Hardcore: " + (isHardcore == 1 ? "True" : "False")); | |
// Remove 4 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
// Retired | |
int isRetired = fileBytes[0]; | |
fileBytes = RemoveFirstByte(fileBytes); | |
Console.WriteLine("Retired: " + (isRetired == 1 ? "True" : "False")); | |
// Remove 30 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 30); | |
// Cheater | |
string isCheaterHex = fileBytes[0].ToString("X2"); | |
Console.Write("Cheater: "); | |
if (isCheaterHex == "4E") | |
{ | |
Console.WriteLine("False"); | |
} | |
else if (isCheaterHex == "D6") | |
{ | |
Console.WriteLine("True"); | |
} | |
else | |
{ | |
Console.WriteLine("Unknown"); | |
} | |
fileBytes = RemoveFirstByte(fileBytes); | |
// Remove 40 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 40); | |
// Character Name | |
short characterNameLength = GetShort(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 2); | |
string characterName = GetString(fileBytes, characterNameLength); | |
fileBytes = RemoveFirstBytes(fileBytes, characterNameLength * 2); | |
Console.WriteLine("Character Name: " + characterName); | |
// Remove 84 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 84); | |
// Character Level | |
int characterLevel = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); // An int is 4 bytes | |
Console.WriteLine("Character Level: " + characterLevel); | |
// XP | |
int characterXP = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("Character XP: " + characterXP); | |
// Fame Level | |
int fameLevel = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("Fame Level: " + fameLevel); | |
// Fame XP | |
int fameXP = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("Fame XP: " + fameXP); | |
// Remove 4 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
// HP | |
int characterHP = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("HP: " + characterHP); | |
// Bonus HP | |
int bonusHP = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("Bonus HP: " + bonusHP); // Always 0 ? | |
// Remove 2 bytes - Unsure | |
fileBytes = RemoveFirstBytes(fileBytes, 2); | |
// Dead | |
short isDead = GetShort(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 2); | |
Console.WriteLine("Is Dead: " + (isDead == 0 ? "Yes" : "No")); | |
// Mana | |
int characterMP = GetInt(fileBytes); | |
fileBytes = RemoveFirstBytes(fileBytes, 4); | |
Console.WriteLine("MP: " + characterMP); | |
} | |
private static byte[] RemoveFirstByte(byte[] byteArray) | |
{ | |
return RemoveFirstBytes(byteArray, 1); | |
} | |
private static byte[] RemoveFirstBytes(byte[] byteArray, int num) | |
{ | |
byte[] newArray = new byte[byteArray.Length - num]; | |
Array.Copy(byteArray, num, newArray, 0, newArray.Length); | |
return newArray; | |
} | |
private static short GetShort(byte[] byteArray) | |
{ | |
byte[] shortLength = new byte[2]; | |
Array.Copy(byteArray, 0, shortLength, 0, 2); | |
short shortVal = BitConverter.ToInt16(shortLength, 0); | |
return shortVal; | |
} | |
private static int GetInt(byte[] byteArray) | |
{ | |
byte[] intLength = new byte[4]; | |
Array.Copy(byteArray, 0, intLength, 0, 4); | |
int shortVal = BitConverter.ToInt32(intLength, 0); | |
return shortVal; | |
} | |
private static string GetString(byte[] byteArray, int length) | |
{ | |
byte[] stringBytes = new byte[length * 2]; // UTF-16LE - Spare 00 after each byte | |
Array.Copy(byteArray, 0, stringBytes, 0, length * 2); | |
return Encoding.Unicode.GetString(stringBytes); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment