Last active
November 13, 2023 01:48
-
-
Save aerosoul94/fddfeffc82cdeadc27512fee47a5c18b to your computer and use it in GitHub Desktop.
Script for extracting League of Legends Solid State Network solid files
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
import xml.etree.ElementTree as ET | |
import zlib | |
import struct | |
import argparse | |
import os | |
class File: | |
def __init__(self, elem): | |
self._file_name = elem.find('FileName').text | |
self._file_index = int(elem.find('FileIndex').text) | |
self._difference_file = elem.find('DifferenceFile').text | |
self._source_hash = elem.find('SourceHash').text | |
self._result_hash = elem.find('ResultHash').text | |
self._offset = int(elem.find('Offset').text) | |
self._file_size = int(elem.find('FileSize').text) | |
self._section_count = int(elem.find('SectionCount').text) | |
def file_name(self): | |
return self._file_name | |
def file_index(self): | |
return self._file_index | |
def difference_file(self): | |
return self._difference_file | |
def source_hash(self): | |
return self._source_hash | |
def result_hash(self): | |
return self._result_hash | |
def offset(self): | |
return self._offset | |
def file_size(self): | |
return self._file_size | |
def section_count(self): | |
return self._section_count | |
class Section: | |
def __init__(self, elem): | |
self._offset = int(elem.find('Offset').text) | |
self._compressed_size = int(elem.find('CompressedSize').text) | |
self._uncompressed_size = int(elem.find('UncompressedSize').text) | |
def offset(self): | |
return self._offset | |
def compressed_size(self): | |
return self._compressed_size | |
def uncompressed_size(self): | |
return self._uncompressed_size | |
class Store: | |
def __init__(self, elem): | |
self._section_list = elem.find('SectionList') | |
self._section_count = int(self._section_list.find('SectionCount').text) | |
self._sections = [] | |
for section in self._section_list.findall('Section'): | |
self._sections.append(Section(section)) | |
def sections(self): | |
return self._sections | |
class TocStore: | |
def __init__(self, tocstore, store): | |
self.store = store | |
self.tree = ET.parse(tocstore) | |
self.root = self.tree.getroot() | |
self.section_size = int(self.root.attrib['SectionSize']) | |
self.file_list = self.root[0] | |
self.store_list = self.root[1] | |
self.files = [] | |
for file in list(self.file_list): | |
self.files.append(File(file)) | |
self.stores = [] | |
self.store_count = int(self.store_list.find('StoreCount').text) | |
for store in self.store_list.findall('Store'): | |
self.stores.append(Store(store)) | |
# match files to their sections | |
self.patcher_store = {} | |
for file in self.files: | |
self.patcher_store[file] = self.get_sections_for_file(file) | |
def get_sections_for_file(self, file): | |
file_size = file.file_size() | |
offset = file.offset() | |
accumulated_size = 0 | |
start = False | |
sections = [] | |
# TODO: use file.section_count() | |
store = self.stores[0] | |
for section in store.sections(): | |
if section.offset() == offset: | |
start = True | |
if start == True: | |
sections.append(section) | |
accumulated_size += section.uncompressed_size() | |
if accumulated_size == file_size: | |
break | |
return sections | |
def extract(self, path): | |
f = open(self.store, 'rb') | |
errors = [] | |
for item in self.patcher_store.items(): | |
file = item[0] | |
sections = item[1] | |
dirname = path + '/' + os.path.dirname(file.file_name()) | |
if not os.path.exists(dirname) and dirname != '': | |
os.makedirs(dirname) | |
file_name = path + '/' + file.file_name() | |
if file.difference_file() == 'TRUE': | |
file_name += ".diff" | |
print(file_name) | |
o = open(file_name, 'wb') | |
for section in sections: | |
#print(section.offset()) | |
f.seek(section.offset() + 0x148) | |
try: | |
data = zlib.decompress(f.read(section.compressed_size())) | |
o.write(data) | |
except Exception as e: | |
errors.append("ERROR: {0} [offset: {1}, size: {2}] ({3})".format(file_name, hex(section.offset()), hex(section.compressed_size()), str(e))) | |
o.close() | |
print("") | |
if len(errors) > 0: | |
print("Extraction completed with {0} errors".format(len(errors))) | |
for error in errors: | |
print(error) | |
print("") | |
def main(): | |
parser = argparse.ArgumentParser(description="Extract LOL files") | |
parser.add_argument('tocstore', type=str, help='Input TOC Store file') | |
parser.add_argument('store', type=str, help='Input Store file') | |
#parser.add_argument('output', type=str, help='Output directory') | |
args = parser.parse_args() | |
tocstore = TocStore(args.tocstore, args.store) | |
output_path = args.tocstore.split('.tocstore')[0] | |
tocstore.extract(output_path) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment