Jump to content

Reading save files?


Recommended Posts

I'm curious how to parse out the save files (e.g. survival_2) with python or lua. I've poked at them and looked through the source and can't figure out how it's encoded. From what i can tell there has been some map editors made but I can't get at the forum links or more information. Base64 yields garbage.

 

It seems like `ENCODE_SAVES = BRANCH ~= "dev"` (main.lua:6) could be changed to something but that's not that useful for most people. 

 

Cheers.

Link to comment
Share on other sites

  • Developer

I'm curious how to parse out the save files (e.g. survival_2) with python or lua. I've poked at them and looked through the source and can't figure out how it's encoded. From what i can tell there has been some map editors made but I can't get at the forum links or more information. Base64 yields garbage.

 

It seems like `ENCODE_SAVES = BRANCH ~= "dev"` (main.lua:6) could be changed to something but that's not that useful for most people. 

 

Cheers.

Check out the code in saveindex.lua.  That should be a good starting point.

Link to comment
Share on other sites

  • Developer

@Cheerio It seems that it's serialized by datadumper then is saved by something in the binary TheSim:SetPersistentString so it's basically magic  :frown:

Although the Alliance would disapprove of me revealing how our tricks are done, here's some code behind the veil:

int SimLuaProxy::SetPersistentString(lua_State* lua ){	const char * fname = luaL_checkstring(lua, 1);	const char * str = luaL_checkstring(lua, 2);		bool should_encode = false;	int cb_idx = 3;	if( lua_isboolean(lua, 3) )	{		should_encode = luaL_checkbool(lua, 3);		cb_idx = 4;	}		std::string tattoo("KLEI");	const int TATTOO_LEN = 4;	const int VER_LEN = 6;	const int HEAD_LEN = 1;	const int len = TATTOO_LEN + VER_LEN + HEAD_LEN;	char prefix[len+1];	snprintf(prefix, len+1, "KLEI%6d%c", 1, should_encode ? 'D' : ' ');	std::string encoded = should_encode ? Util::ZipAndEncodeString(str) : str;		//barf.	encoded = prefix + encoded;	unsigned int cbidx = INVALID;	if (lua_isfunction(lua, cb_idx))	{		lua_pushvalue(lua, cb_idx); //push a copy of the callback function to the top of the stack for lauL_ref		cbidx = luaL_ref(lua, LUA_REGISTRYINDEX);	}	//cSimulation::OnSaveFileResult	mSim->GetGame()->GetPersistentStorage()->SaveFile(fname,encoded.c_str(), encoded.length(), fastdelegate::MakeDelegate( this, &SimLuaProxy::OnSaveStringComplete), cbidx);	return 0;}
void PersistentStorage::SaveFile(const char * filename, const char * data, unsigned int len, FileOpCallback cb, unsigned int userdata){	if (!mReady)	{		LOGERR("Trying to use file system too early!");		cb(false, filename,0, 0, userdata);		return;	}	std::string fname = GetSaveDir();	fname += filename;	FILE * f = fopen(fname.c_str(),"wb");	if (f)	{		fwrite(data, len,1,f);		fclose(f);		cb(true,fname.c_str(),0,0, userdata);		return;	}	else	{		cb(false,fname.c_str(),0,0, userdata);		return;	}}
Link to comment
Share on other sites

  • Developer

For those as confused as me, that's not Lua. :lemo:

It's some of our c++ code that gets called when you call 'TheSim:SetPersistentString'.  Basically whenever you call 'TheSim:SomeFunction', there's some c++ code that runs behind the scenes that lives in 'SimLuaProxy'.

Link to comment
Share on other sites

For those of you without a C++ background, this is how those functions would look like in Lua:

function SimLuaProxy:SetPersistentString(fname, str, should_encode, cb)	assert(type(fname) == "string", "Bad argument #1 do SetPersistentString, string expected, got whatever.")	assert(type(str) == "string", "Bad argument #2 do SetPersistentString, string expected, got whatever.")		if type(should_encode) ~= "boolean" then		cb, should_encode = should_encode, false	end	local tattoo = "KLEI"	local prefix = ("%s%6d%s"):format(tattoo, 1, should_encode and "D" or " ")	local encoded = should_encode and Util.ZipAndEncodeString(str) or str	encoded = prefix .. encoded	if type(cb) ~= "function" then		cb = nil	end	mSim:GetGame():GetPersistentStorage():SaveFile(fname, encoded, fastdelegate.MakeDelegate( self, self.OnSaveStringComplete ), cb)end
function PersistentStorage:SaveFile(filename, data, cb, userdata)	if not self.mReady then		io.stderr:write("Trying to use file system too early!")		cb(false, filename, 0, 0, userdata)		return	end	local fname = self:GetSaveDir()	fname = fname .. filename	local f = io.open(fname, "wb")	if f then		f:write(data)		f:close()		cb(true, fname, 0, 0, userdata)		return	else		cb(false, fname, 0, 0, userdata)	endend
Note that the original callback gets passed as the "userdata" for the effective callback used in SaveFile. Note also thet "zip and encode" means "zip it and convert it to base 64 encoding".

But @Cheerio, I'm assuming then that you're using some wrapper/API intermediate to the Lua C API, since the object ("this") in SimLuaProxy::SetPersistentString would not be directly accessible if you were just lua_pushcfunction()ing it (in fact, it'd need to be a class function for that, not a member one).

Link to comment
Share on other sites

  • Developer

For those of you without a C++ background, this is how those functions would look like in Lua:

function SimLuaProxy:SetPersistentString(fname, str, should_encode, cb)	assert(type(fname) == "string", "Bad argument #1 do SetPersistentString, string expected, got whatever.")	assert(type(str) == "string", "Bad argument #2 do SetPersistentString, string expected, got whatever.")		if type(should_encode) ~= "boolean" then		cb, should_encode = should_encode, false	end	local tattoo = "KLEI"	local prefix = ("%s%6d%s"):format(tattoo, 1, should_encode and "D" or " ")	local encoded = should_encode and Util.ZipAndEncodeString(str) or str	encoded = prefix .. encoded	if type(cb) ~= "function" then		cb = nil	end	mSim:GetGame():GetPersistentStorage():SaveFile(fname, encoded, fastdelegate.MakeDelegate( self, self.OnSaveStringComplete ), cb)end
function PersistentStorage:SaveFile(filename, data, cb, userdata)	if not self.mReady then		io.stderr:write("Trying to use file system too early!")		cb(false, filename, 0, 0, userdata)		return	end	local fname = self:GetSaveDir()	fname = fname .. filename	local f = io.open(fname, "wb")	if f then		f:write(data)		f:close()		cb(true, fname, 0, 0, userdata)		return	else		cb(false, fname, 0, 0, userdata)	endend
Note that the original callback gets passed as the "userdata" for the effective callback used in SaveFile. Note also thet "zip and encode" means "zip it and convert it to base 64 encoding".

But @Cheerio, I'm assuming then that you're using some wrapper/API intermediate to the Lua C API, since the object ("this") in SimLuaProxy::SetPersistentString would not be directly accessible if you were just lua_pushcfunction()ing it (in fact, it'd need to be a class function for that, not a member one).

 

There is some other magic for the binding.  I've never looked into it myself :).

Link to comment
Share on other sites

Hello,

 

i've been trying to decode the save index file without success. i have reversed a bit of the binary code (ZipAndEncode as well as DecodeAndUnzip), and as far as i can understand the assembly, references to ZLib's inflate / deflate are used.

 

However, after any possible tests on a sample save index file, i'm still unable to decode the data. i have two questions:

- As far as i can understand, the 11 first bytes needs to be stripped prior to base 64-decoding the data ("KLEI[5 padding spaces]1D"). Is that right?

- Then, what kind of base64 encoding implementation is used? It seems that the decoded data is wrong... What are the plus and slash ( + / ) signs used to encode? Spaces / Numbers?

- i might have been able to base64 decode the data, however the result don't look like a proper ZLib header... Or the compression method isn't known to the standard ZLib library. What would be the way to inflate the data back to it's original state?

 

Is there anyone successful in such process yet?

Thanks,

Pierre.

Link to comment
Share on other sites

Oh and for those interested, here is the code i'm using to play with the save file, it's ruby (Easy sandboxing).

 

require 'base64'require 'zlib' def inflate (string)  zstream = Zlib::Inflate.new  buf = zstream.inflate string  zstream.finish  zstream.close  bufend # Have been playing around with this offset...res = File.read('/Volumes/Data/Users/me/Documents/Klei/DoNotStarve/save/saveindex')[11..-1]puts "Save index has been read (#{res.length} bytes)." # As well as this one.res = Base64.decode64(res)[0..-1]puts "Data was decoded (#{res.length} bytes)." # But this call always throws either bad header, or unknown compression method.puts inflate res
Link to comment
Share on other sites

Okay, so i have created a small program of nested loops to try and find the viable offsets for decoding the format. It worked. For those interested here is the "search" program:

 

(0..30).each do |outer|  fres = File.read('/Volumes/Data/Users/me/Documents/Klei/DoNotStarve/save/saveindex')[outer..-1]  (0..30).each do |inner|    bres = Base64.decode64(fres)[inner..-1]    (0..30).each do |smaller|      begin        dec = inflate bres[smaller..-1]        puts "VIABLE OFFSET SET FOUND: #{outer} #{inner} #{smaller}"      rescue Exception => e      end    end  endend[code] The valid offset found are:[code]2 0 192 1 182 2 172 3 162 4 152 5 142 6 132 7 122 8 112 9 102 10 92 11 82 12 72 13 62 14 52 15 42 16 32 17 22 18 12 19 011 0 1611 1 1511 2 1411 3 1311 4 1211 5 1111 6 1011 7 911 8 811 9 711 10 611 11 511 12 411 13 311 14 211 15 111 16 015 0 1315 1 1215 2 1115 3 1015 4 915 5 815 6 715 7 615 8 515 9 415 10 315 11 215 12 115 13 019 0 1019 1 919 2 819 3 719 4 619 5 519 6 419 7 319 8 219 9 119 10 023 0 723 1 623 2 523 3 423 4 323 5 223 6 123 7 027 0 427 1 327 2 227 3 127 4 0[/code] If i use the first one (11 0 16) i am able to decode the save file. i'm not using the first offset (2) because i suspect that it won't work on other implementations of base64 decoders (It won't remove all the tattoo part of KLEI etc, and only worked on my computer because of the specific ruby base64 boundaries checks). With that in mind, here is how i can now read the save files:[code]# Let's use offset 11.ruby finds the required boundariesres = File.read('/Volumes/Data/Users/me/Documents/Klei/DoNotStarve/save/saveindex')[11..-1]puts "Save index has been read (#{res.length} bytes):"# Now we use index 0 (So no offset at all).res = Base64.decode64(res)puts "Data was decoded (#{res.length} bytes):"# Now, we want to use index 16:puts inflate res[16..-1][/code] The result is something like this:[code]return {  slots={    {      character="wendy",      continue_pending=false,      current_mode="survival",      modes={        survival={          day=27,          file="survival_1",          files={ "survival_1" },          options={            preset="SURVIVAL_DEFAULT",            tweak={ misc={ season="onlysummer" }, unprepared={ berrybush="often", carrot="often" } }           },          world=1         }       },      resurrectors={  },      save_id="unknownID-1384189507-1"     },    {      character="wendy",      continue_pending=false,      current_mode="survival",      modes={        survival={          day=1,          file="survival_2",          files={ "survival_2" },          options={            preset="SURVIVAL_DEFAULT",            tweak={ misc={ season="onlysummer" }, unprepared={ berrybush="often", carrot="often" } }           },          world=1         }       },      resurrectors={  },      save_id="unknownID-1384272176-2"     },    { modes={  }, save_id="unknownID-1384189508-3" },    { modes={ survival={  } }, save_id="unknownID-1383853651-4" },    screecher={ modes={  } }   } }

 

Success! Hope that will help.

Pierre.

Link to comment
Share on other sites

@hickscorp

Good job, man. I hadn't attempted to decode/inflate save files myself, so I was unaware of the need to offset the base64 decoded string. I wonder what those initial 16 initial bytes mean, though.

If anyone's interested, here's a Lua implementation (working essentially like your Ruby one):

#!/usr/bin/lualocal i_fname, o_fname = ...local ifh = i_fname and i_fname ~= "-" and io.open(i_fname, "rb") or io.stdinlocal ofh = o_fname and o_fname ~= "-" and io.open(o_fname, "wb") or io.stdoutofh:write((	require("zlib").inflate()(		require("base64").decode(			ifh:read("*a"):sub(12)		):sub(17)	)))
It uses this library for base64 decoding and this binding to zlib.
Link to comment
Share on other sites

Good job, man. I hadn't attempted to decode/inflate save files myself, so I was unaware of the need to offset the base64 decoded string. I wonder what those initial 16 initial bytes mean, though.

Thanks.

 

i have created a small script that decodes any input file by path. It checks if it's encoded first (There is another format used by Don't Starve which starts with "KLEI     1 " instead of "KLEI     1D" which isn't encoded at all).

 

The script requires that the user configures the base path of the don't starve data folder. Then you can call it like this:

./decoder.rb save/saveindex

 

It will create a file named "save_saveindex.txt" containing the decoded data.

 

#!/usr/bin/env ruby require 'base64'require 'zlib'require 'optparse' # Change this to your Don't Starve data directory.BASEPATH        = '/Volumes/Data/Users/me/Documents/Klei/DoNotStarve' def decode (path)  # Read file contents.  contents      = File.read "#{BASEPATH}/#{path}"  # Remove tattoo header and version 'KLEI     1'.  res           = contents[10..-1]  # If file is directly readable, just remove the leading ' ' and that's it.  decode        = res.start_with? 'D'  res           = res[1..-1]  # Return contents immediattelly if content doesn't require decodding.  return res    unless decode   # Try to find offsets tripplet.  # find            = false  # if find  #   tries         = 0  #   viable        = []  #   (0..32).each do |i|  #     next unless i_res = contents[i..-1]  #     (0..32).each do |j|  #       next unless j_res = Base64.decode64(i_res)[j..-1]  #       (0..32).each do |k|  #         begin  #           tries   += 1  #           viable  << [ i, j, k ] if Zlib.inflate(j_res[k..-1])  #         rescue Exception => e  #         end  #       end  #     end  #   end  #   puts "Attempts: #{tries}."  #   puts "Viable: #{viable.length}."  #   puts "Offsets: #{viable}"  # end   # Base 64 decode + Inflate.  Zlib.inflate Base64.decode64(res)[16..-1]end # Check arguments.if ARGV.count!=1  puts 'Usage: decoder.rb filename'  puts 'Eg.: ./decoder.rb save/saveindex'end res             = decode fn = ARGV[0]File.open "#{fn.gsub '/', '_'}.txt", 'wb' do |f|  f.write resend

 

I hope it will help people.

Pierre.

Link to comment
Share on other sites

The files stored by the game are some kind of LUA-specific serialisation. It isn't much compatible with anything around except LUA. It is a security risk, because i guess someone could inject any LUA code into the save files.

 

i would myself rather use either XML or JSON to serialize data only, and don't use an eval on it.

 

Anyway, i'm wondering: is there any parser around (Not LUA-based) that would convert the enclosed example to JSON or to PLIST or XML?

 

Thanks,

Pierre.

 

Format data looks like this:

return {  slots={    {      character="wendy",      continue_pending=false,      current_mode="survival",      modes={        survival={          day=27,          file="survival_1",          files={ "survival_1" },          options={            preset="SURVIVAL_DEFAULT",            tweak={ misc={ season="onlysummer" }, unprepared={ berrybush="often", carrot="often" } }           },          world=1         }       },      resurrectors={  },      save_id="unknownID-1384189507-1"     },    {      character="wendy",      continue_pending=false,      current_mode="survival",      modes={        survival={          day=1,          file="survival_2",          files={ "survival_2" },          options={            preset="SURVIVAL_DEFAULT",            tweak={ misc={ season="onlysummer" }, unprepared={ berrybush="often", carrot="often" } }           },          world=1         }       },      resurrectors={  },      save_id="unknownID-1384272176-2"     },    { modes={  }, save_id="unknownID-1384189508-3" },    { modes={ survival={  } }, save_id="unknownID-1383853651-4" },    screecher={ modes={  } }   } }
Link to comment
Share on other sites

It checks if it's encoded first (There is another format used by Don't Starve which starts with "KLEI     1 " instead of "KLEI     1D" which isn't encoded at all).

Yes, that's in the snippet Cheerio posted. I just assumed people wouldn't try to decode a file that's already decoded. ;P

But your solution is quite helpful for batch processing, of course.

Link to comment
Share on other sites

The files stored by the game are some kind of LUA-specific serialisation. It isn't much compatible with anything around except LUA. It is a security risk, because i guess someone could inject any LUA code into the save files.

It's not a security risk because it runs in an empty environment (more precisely, it runs in an environment with just loadstring(), needed for function serialization). The serializer used is this one. You can't always convert it to JSON/XML precisely because it can store functions (even closures), metatables, cyclic references and tables using arbitrary keys, but provided these features are not being used you can just grab the return value of the file (which is a Lua table) and pass it to basically any json serializer. I don't see the point, though.

EDIT:

Actually, it is a security risk due to the presence of loadstring(). Originally it wasn't there, with save files running in a completely empty environment, but was added to allow function serialization. However, since loadstring() loads a chunk in the global environment it breaches sandboxing. Someone could inject code like

loadstring("return _G")().YOU_RE = "SCREWED"
Link to comment
Share on other sites

Dear Simplex,

 

Thanks for the explanations.

 

You can't always convert it to JSON/XML precisely because it can store functions (even closures), metatables, cyclic references and tables using arbitrary keys, but provided these features are not being used you can just grab the return value of the file (which is a Lua table) and pass it to basically any json serializer. I don't see the point, though.

Understood. The point: deserializing and serializing back the data from a C++ application. i've began to create a ANTLR4 grammar for the LUA dumped data, provided that there are no functions nor closures in the dump. Maybe i'll post it for people programming in C / C++ / Objective-C like me ;-)

 

Thanks and best to you,

Pierre.

Link to comment
Share on other sites

Hello again party people :-)
 
i have a good draft for the LUA parser. i'm using ANTLR for generating parsers. The grammar is as follows:

grammar LuaTable; options {  language=ObjC;  output = AST;}tokens {    STRING; NUMBER; OBJECT; FIELD; IDENT;    TRUE; FALSE; NULL;    COMMA  = ',';} fragment Letter : ('a'..'z') | ('A'..'Z');fragment Digit : '0'..'9';fragment Special: ('_' | '-'); // Ignored whites.WS : (' '|'\n'|'\r'|'\t')+ {$channel=HIDDEN;};// Types.Ident : ( Letter | Special )+;String   :  '"' ( ~('\u0000'..'\u001f' | '\\' | '\"' ) )* '"';Number   :  '-'? Digit+ ( '.' Digit* )?; main : object;object   : '{' members? '}' -> ^(OBJECT members?);members  : pairOrTable (COMMA! pairOrTable)*;pairOrTable : (ident '=')? value -> ^(FIELD ident? value); ident : Ident -> IDENT;value : object | string | number | 'true' -> TRUE | 'false' -> FALSE | 'null' -> NULL;string   : String -> ^(STRING String);number   : Number -> ^(NUMBER Number);

 
This outputs the attached Objective-C parser. For those interested, i also have enclosed the generated Java code. It can also generate C++, but if you're interested in that, i bet you know how to generate it from the ANTLR grammar ;)
 

Objective-C:

Tokens: http://pastebin.com/MWTuJSSj

Lexer.h/.m: http://pastebin.com/VAY5TeHS and http://pastebin.com/0zAdMGbG

Parse.h/.m: http://pastebin.com/qAEtrgzN and http://pastebin.com/eFjFctDs

 

Java:

Tokens:http://pastebin.com/KRVEjgu5

Lexer.java: http://pastebin.com/X6Rr0TsG

Parser.java: http://pastebin.com/hb2ZjrkQ

 

Best to you all!
Pierre.

Link to comment
Share on other sites

@hickscorp

The nonterminal symbol you're using for identifiers is not accurate (and since it's arbitrarily ). '-' can't appear in it, and digits can. They're just C identifiers, i.e. strings accepted by (in Perl-inspired regex syntax):

[_\a][_\w]*

Lua has several other quoting mechanisms for strings, but since those are not used in the save format you're fine.

pairOrTable is also incomplete, since the key need not be an identifier. The relevant part from dumper.lua is this:

  local function make_key(t, key)    local s    if type(key) == 'string' and key:match('^[_%a][_%w]*$') then      s = key .. "="    else      s = "[" .. dumplua(key, 0) .. "]="    end    t[key] = s    return s  end
the key-value pair will use the format you're assuming if the key is a string corresponding to a valid identifier, but otherwise the key will be its serialization surrounded by brackets.

There may also be other issues with the grammar, I just took a brief look at it. You can find the complete syntax of Lua 5.1 (except for operator precedence) here, in extended BNF. But I don't see what you're trying to achieve with this. The save data is already a tree (provided it's not representing cyclic references, but this could only possibly occur with mod savedata, since the game's own savedata has no cycles), amenable to DFS, structural pattern matching and whatever else you could want to do with a tree.

If for some reason (I don't understand) you really want to get ASTs out of it (which would actually be CSTs, technically speaking), there are many Lua parsers already out there. The most robust and extensible of those is certainly the one from the Metalua project, implemented through pure Lua libraries. But it's also the most complex one to understand, since it's based on parser combinators (if you're familiar with Haskell's Parsec library you should be fine, since it's heavily based on it), so alternative ones might be preferred.

Link to comment
Share on other sites

Let me summarize all the steps needed to decode save file:

  1. Open save file.
  2. Skip first 11 bytes.
  3. Decode file using Base64 decoding method.
  4. Skip first 16 bytes.
  5. Unzip file.

 

Am I correct?

Try to follow these steps on C# but fails on unzipping.

Link to comment
Share on other sites

Let me summarize all the steps needed to decode save file:

  • Open save file.
  • Skip first 11 bytes.
  • Decode file using Base64 decoding method.
  • Skip first 16 bytes.
  • Unzip file.
Am I correct?

Try to follow these steps on C# but fails on unzipping.

This is correct. My guess would be you're having issues in the base64 decoding, since there are several different conventions for the last two characters (uppercase letters, lowercase letters and digits total 62 symbols, so 2 more are used). Otherwise, it might be that zlib stores some header metadata which is incompatible with your zip library. Are you using zlib?

Link to comment
Share on other sites

This is correct. My guess would be you're having issues in the base64 decoding, since there are several different conventions for the last two characters (uppercase letters, lowercase letters and digits total 62 symbols, so 2 more are used). Otherwise, it might be that zlib stores some header metadata which is incompatible with your zip library. Are you using zlib?

 

I'm using GZip method.

But I found out that the problems appears before the unzipping. That what the Exception says:

 

The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.

 

So, your guess was right. What symbols may prevents the decoding?

Link to comment
Share on other sites

I'm using GZip method.

But I found out that the problems appears before the unzipping. That what the Exception says:

GZip is not compatible with "regular" zip (hence the different extension, usually ".gz"). Both use the DEFLATE algorithm, but the metadata format is different.

So, your guess was right. What symbols may prevents the decoding?

The save files use the characters '+' and '/' as digits representing 62 and 63, respectively.

Link to comment
Share on other sites

Let me summarize all the steps needed to decode save file:

  • Open save file.
  • Skip first 11 bytes.
  • Decode file using Base64 decoding method.
  • Skip first 16 bytes.
  • Unzip file.
Am I correct?

Try to follow these steps on C# but fails on unzipping.

 

Try using DotNetZip library, this is the way I'm reading and it always worked.

// Read the file headerusing (var reader = new BinaryReader(fileStream)){    saveData.FileHeader.Magic = reader.ReadInt32();    saveData.FileHeader._padd1 = reader.ReadChar();    saveData.FileHeader._padd2 = reader.ReadChar();    saveData.FileHeader._Unknown = reader.ReadInt32();    saveData.FileHeader.Encoded = reader.ReadChar();    using (var sreader = new StreamReader(fileStream))        remainingData = sreader.ReadToEnd();}if (saveData.FileHeader.Encoded == 'D'){    // We need to do base64 deconvert first    byte[] data = StringConvert.FromBase64ToBytes(remainingData);    // Create a new stream    var memStream = new MemoryStream(data);    // Read the save header    using (var reader = new BinaryReader(memStream))    {        saveData.SaveHeader.Version = reader.ReadInt32();        saveData.SaveHeader.HeaderSize = reader.ReadInt32();        saveData.SaveHeader.UncompressedSize = reader.ReadInt32();        saveData.SaveHeader.CompressedSize = reader.ReadInt32();        // Decompress the content        var zlibstream = new ZlibStream(memStream, CompressionMode.Decompress);        using (var sreader = new StreamReader(zlibstream))            remainingData = sreader.ReadToEnd();    }}// By here, remainingData contains the actual save file data

PS. FromBase64ToBytes is just a wrapper over System.Convert.FromBase64String

Link to comment
Share on other sites



#!/usr/bin/env python

# -*- coding: utf-8 -*-

 

from base64 import b64decode

from sys import argv

from os import path

 

if __name__ == '__main__':

    if len(argv)>1:

        try: open(path.join(path.dirname(path.realpath(argv[0])),'my_output_file.txt'),'w').write(b64decode(open(argv[1], 'rb').read()[11:])[16:].decode('zlib'))

        except Exception, e: print(e)

 


Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

×
  • Create New...