Skip to content

Instantly share code, notes, and snippets.

@xchewtoyx
Last active May 18, 2024 05:54
Show Gist options
  • Save xchewtoyx/ab04f1148995e76a0719f5ffb06ec7e5 to your computer and use it in GitHub Desktop.
Save xchewtoyx/ab04f1148995e76a0719f5ffb06ec7e5 to your computer and use it in GitHub Desktop.
Module to load Rocksmith+ IDA files
import parseida
barracuda = '421bdc20-4dcd-4752-b035-12c79cfebc38'
ida = skill_loader(barracuda)
print(f"Loaded {ida.filename}")
for arrangement_key in ida.arrangement_keys():
print(f"Difficulty({arrangement_key}): {ida.song_difficulty[arrangement_key][0]['rank_name']}")
print(f"Techniques({arrangement_key}): {ida.technique_list(arrangement_key)}")
Loaded /mnt/c/Users/russell/AppData/Local/My Games/Rocksmith+/DLC/Skill/v43/421bdc20-4dcd-4752-b035-12c79cfebc38_v36.ida
Difficulty(ps:ad:top): basic
Techniques(ps:ad:top): ['tech:Guitar:CHD_BMM:x,1,3,3,3,x', 'tech:Guitar:CHD_OCB:x,0,2,2,2,0', 'tech:Guitar:CHD_P3:1,3,3,x,x,x', 'tech:Guitar:CHD_P3:x,1,3,3,x,x', 'tech:Guitar:CHD_PO:0,2,2,x,x,x', 'tech:Guitar:CHD_PO:x,0,2,2,x,x']
Difficulty(ps:cc:bc): basic
Techniques(ps:cc:bc): ['tech:Guitar:CHD_OCB:x,0,2,2,2,0', 'tech:Guitar:CHD_P3:1,3,3,x,x,x', 'tech:Guitar:CHD_P3:x,1,3,3,x,x', 'tech:Guitar:CHD_PO:0,2,2,x,x,x', 'tech:Guitar:CHD_PO:x,0,2,2,x,x']
Difficulty(ps:cc:fc): basic
Techniques(ps:cc:fc): ['tech:Guitar:CHD_BMM:x,1,3,3,3,x', 'tech:Guitar:CHD_OCB:x,0,2,2,2,0', 'tech:Guitar:CHD_P3:1,3,3,x,x,x', 'tech:Guitar:CHD_P3:x,1,3,3,x,x', 'tech:Guitar:CHD_PO:0,2,2,x,x,x', 'tech:Guitar:CHD_PO:x,0,2,2,x,x']
Difficulty(ps:cc:pc): basic
Techniques(ps:cc:pc): ['tech:Guitar:CHD_DBE:0,2,x,x,x,x', 'tech:Guitar:CHD_DBE:1,3,x,x,x,x', 'tech:Guitar:CHD_DBE:x,0,2,x,x,x', 'tech:Guitar:CHD_DBE:x,1,3,x,x,x', 'tech:Guitar:CHD_P2A:x,1,3,x,x,x', 'tech:Guitar:CHD_P2E:1,3,x,x,x,x', 'tech:Guitar:CHD_PO:0,2,x,x,x,x', 'tech:Guitar:CHD_PO:x,0,2,x,x,x']
Difficulty(ps:cc:sn): basic
Techniques(ps:cc:sn): []
import glob
import json
import os
import re
import zlib
# IDA file header is:
# 4 bytes - "ADI "
# 4 bytes - i32 1 (file version?)
# 4 bytes - i32 compressed data length
# 4 bytes - i32 uncompressed size
# 4 bytes - i32 number of files in archive
# 20 bytes - string ISO timestamp (e.g. "2024-02-08T00:00:00")
# 8 bytes - 0xffffffffffffffff (i64 -1)
from struct import unpack_from
DLC_BASE="/mnt/c/Users/russell/AppData/Local/My Games/Rocksmith+/DLC"
class IDAReader():
def __init__(self, filename):
self.filename = filename
with open(filename, 'rb') as file:
self.file_content = file.read()
# Read file count from file header (u32 at offset 0x10)
file_count = unpack_from('I', self.file_content, 0x10)[0]
# Identify the position of zlib signature (x\x9c)
# Header is 48 bytes, so this should be at 0x30
compressed_data_offset = self.file_content.find(b'x\x9c')
# Extract the compressed data starting from the zlib signature
compressed_data = self.file_content[compressed_data_offset:]
# Decompress the data using zlib
self.decompressed_data = zlib.decompress(compressed_data)
self.files = {}
self.data_offset = -1
self._parse_ida_index(file_count)
# File index entry format
# start - i32 offset into where file entry starts
# length - i32 length in bytes of file
# crc32 - u32 CRC32 of file (use zlib.crc32 to verify)
# filename
# length - i32 length of filename
# path - bytes[] byte array containing path and name of the file
def _parse_ida_index(self, file_count):
progress = 0
for _ in range(file_count):
header = self.decompressed_data[progress:progress+16]
progress += 16
start, length, crc32, flen = unpack('iiIi', header)
filename = self.decompressed_data[progress:progress+flen].decode('utf-8')
self.files[filename] = dict(start=start, length=length, data=data)
progress += flen
# Progress indicator is now pointing to the start of the data
self.data_offset = progress
def extract_json_data(self, filename):
attrs = self.files[filename]
offset = self.data_offset + attrs["start"]
end = offset+attrs["length"]
return json.loads(
self.decompressed_data[offset:end]
)
class SkillIDA(IDAReader):
def __init__(self, *args):
super().__init__(*args)
self.song_difficulty = self.extract_json_data(next(key for key in self.files.keys() if 'curriculum_song_difficulty' in key))
self.song_nexus_aegis = self.extract_json_data(next(key for key in self.files.keys() if 'curriculum_song_nexus_aegis' in key))
def arrangement_keys(self):
return self.song_difficulty.keys()
def technique_list(self, arrangement_key):
return [f"{tech['key_branch']}:{tech['key_nexus']}" for tech in self.song_nexus_aegis.get(arrangement_key, [])]
def skill_loader(arrangementid):
skills_path = os.path.join(DLC_BASE, 'Skill')
candidates = glob.glob(f'{skills_path}/**/{arrangementid}_v*.ida')
# In the case of duplicates, return the filename that sorts highest
return SkillIDA(max(candidates))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment