Skip to content

Instantly share code, notes, and snippets.

@illusion0001
Last active June 9, 2025 05:27
Show Gist options
  • Select an option

  • Save illusion0001/e32861917f437938a740d25799511140 to your computer and use it in GitHub Desktop.

Select an option

Save illusion0001/e32861917f437938a740d25799511140 to your computer and use it in GitHub Desktop.
Scripts to manipulate Gravity Rush 2 savefiles
#!python3
# Script to transcribe data bin to json. From Null @ Team Alua
import sys
import struct
from collections import OrderedDict
def unpack(upstream_data_set):
global loaded_data
loaded_data = loaded_data + 1
currentCursor = file.tell()
# print(hex(file.tell()))
file.seek(int.from_bytes(file.read(4), byteorder='little'), 0)
variable_name = file.read(200).split(b'\x00')[0].decode('UTF8') #Use UTF8 because some strings are in Japanese
# print(hex(file.tell()))
file.seek(currentCursor + 4, 0)
type = int.from_bytes(file.read(4), byteorder='little')
data_location = file.tell()
if type == 0x08: # List
list_length = int.from_bytes(file.read(4), byteorder='little')
name_hash = file.read(4).hex()
data_location = file.tell()
value = {}
for i in range(0, list_length):
unpack(value)
value = OrderedDict(sorted(value.items()))
else:
if type % 0x10 == 0x0b: # String
string_length = int.from_bytes(file.read(4), byteorder='little') - 1
data_location = type // 0x10
file.seek(data_location, 0)
try:
value = file.read(string_length).decode('UTF8')
if(variable_name == "playtime"):
print("Player playtime: " + value)
except:
value = "ERROR EXTRACTING STRING"
value = data_location
file.seek(currentCursor + 0x0c, 0)
elif type == 0x09: # Float
value = struct.unpack('f', file.read(4))[0]
value = data_location
elif type == 0x0C: # Boolean
value = int.from_bytes(file.read(1), byteorder='little') > 0
value = data_location
file.seek(3, 1)
else:
raw_value = int.from_bytes(file.read(4), byteorder='little')
if(raw_value == 0x10): #Vector
data_location = type // 0x10
file.seek(data_location, 0)
#value = struct.unpack('ffff', file.read(16))
value = data_location
file.seek(currentCursor + 0x0c, 0)
else:
value = "Unknown type " + hex(type) + " with value " + raw_value.hex()
input("Warring!!! Unknow type!!! %s at %s with value %s" % (hex(type), hex(file.tell()-8), raw_value.hex()))
print()
name_hash = file.read(4).hex()
if variable_name == None:
variable_name = hex(data_location)
upstream_data_set[variable_name] = value
def getOffset(location):
# print(location)
locallist = data_set
for name in location:
locallist = locallist[name]
# print(hex(locallist))
return locallist
def main():
if not sys.argv[1]:
from tkinter.filedialog import askopenfilename
file_path = askopenfilename()
else:
file_path = sys.argv[1]
global loaded_data
global file
global data_set
edit_list = []
print("File loaded: " + file_path)
print("Add online items? Y/N")
add_online = input()
if add_online == "Y" or add_online == "y":
add_online = True
edit_list += [
[["StatisticalChart","DustyToken"], 99999.0],
[["CostumeUnlock","kit02"], True],
[["CostumeUnlock","kit03"], True],
[["CostumeUnlock","kit05"], True],
[["CostumeUnlock","kit06"], True],
[["CostumeUnlock","kit07"], True],
[["CostumeUnlock","kit08"], True],
[["CostumeUnlock","kit10"], True],
[["CostumeUnlock","kit13"], True],
[["CostumeUnlock","kit14"], True],
[["CostumeUnlock","kit15"], True],
[["CostumeUnlock","kit16"], True],
[["CostumeUnlock","kit18"], True],
[["Gesture","item_01"], True],
[["Gesture","item_02"], True],
[["Gesture","item_03"], True],
[["Gesture","item_04"], True],
[["Gesture","item_05"], True],
[["Gesture","item_06"], True],
[["Gesture","item_07"], True],
[["Gesture","item_08"], True],
[["Gesture","item_09"], True],
[["Gesture","item_10"], True],
[["Gesture","item_11"], True],
[["Gesture","item_12"], True],
[["Gesture","item_13"], True],
[["Gesture","item_14"], True],
[["Gesture","item_15"], True],
[["Gesture","item_16"], True],
[["Gesture","item_17"], True],
[["Gesture","item_18"], True],
[["Gesture","item_19"], True],
[["Gesture","item_20"], True],
[["HomeInfo", "Unlock", "kagu_01_01"], True],
[["HomeInfo", "Unlock", "kagu_01_02"], True],
[["HomeInfo", "Unlock", "kagu_01_03"], True],
[["HomeInfo", "Unlock", "kagu_01_04"], True],
[["HomeInfo", "Unlock", "kagu_02_01"], True],
[["HomeInfo", "Unlock", "kagu_02_02"], True],
[["HomeInfo", "Unlock", "kagu_02_03"], True],
[["HomeInfo", "Unlock", "kagu_02_04"], True],
[["HomeInfo", "Unlock", "kagu_03_01"], True],
[["HomeInfo", "Unlock", "kagu_03_02"], True],
[["HomeInfo", "Unlock", "kagu_03_03"], True],
[["HomeInfo", "Unlock", "kagu_03_04"], True],
[["HomeInfo", "Unlock", "kagu_04_01"], True],
[["HomeInfo", "Unlock", "kagu_04_02"], True],
[["HomeInfo", "Unlock", "kagu_04_03"], True],
[["HomeInfo", "Unlock", "kagu_04_04"], True],
[["HomeInfo", "Unlock", "kagu_05_01"], True],
[["HomeInfo", "Unlock", "kagu_05_02"], True],
[["HomeInfo", "Unlock", "kagu_05_03"], True],
[["HomeInfo", "Unlock", "kagu_05_04"], True],
[["HomeInfo", "Unlock", "kagu_06_01"], True],
[["HomeInfo", "Unlock", "kagu_06_02"], True],
[["HomeInfo", "Unlock", "kagu_06_03"], True],
[["HomeInfo", "Unlock", "kagu_06_04"], True],
[["HomeInfo", "Unlock", "kagu_07_01"], True],
[["HomeInfo", "Unlock", "kagu_07_02"], True],
[["HomeInfo", "Unlock", "kagu_07_03"], True],
[["HomeInfo", "Unlock", "kagu_07_04"], True],
[["HomeInfo", "Unlock", "kagu_08_01"], True],
[["HomeInfo", "Unlock", "kagu_08_02"], True],
[["HomeInfo", "Unlock", "kagu_08_03"], True],
[["HomeInfo", "Unlock", "kagu_08_04"], True],
[["HomeInfo", "Unlock", "kagu_09_01"], True],
[["HomeInfo", "Unlock", "kagu_09_02"], True],
[["HomeInfo", "Unlock", "kagu_09_03"], True],
[["HomeInfo", "Unlock", "kagu_10_01"], True],
[["HomeInfo", "Unlock", "kagu_11_01"], True],
[["HomeInfo", "Unlock", "kagu_12_01"], True],
[["HomeInfo", "Unlock", "kagu_13_01"], True],
[["PhotoItem", "item_01"], True],
[["PhotoItem", "item_02"], True],
[["PhotoItem", "item_03"], True],
[["PhotoItem", "item_04"], True],
[["PhotoItem", "item_05"], True],
[["PhotoItem", "item_06"], True],
[["PhotoItem", "item_07"], True],
[["PhotoItem", "item_08"], True],
[["PhotoItem", "item_09"], True],
[["PhotoItem", "item_10"], True],
[["PhotoItem", "item_11"], True],
[["PhotoItem", "item_12"], True],
[["PhotoItem", "item_13"], True],
[["PhotoItem", "item_14"], True],
[["PhotoItem", "item_15"], True],
[["PhotoItem", "item_16"], True],
[["PhotoItem", "item_17"], True],
[["PhotoItem", "item_18"], True],
[["PhotoItem", "item_19"], True],
[["PhotoItem", "item_20"], True],
[["PhotoItem", "item_21"], True],
[["PhotoItem", "item_22"], True],
[["PhotoItem", "item_23"], True],
[["PhotoItem", "item_24"], True],
[["PhotoItem", "item_25"], True],
[["PhotoItem", "item_26"], True],
[["PhotoItem", "item_27"], True],
[["PhotoItem", "item_28"], True],
[["PhotoItem", "item_29"], True],
[["Talisman", "Slot997"], [4450.0, 24832.0, 0.0, 0.0]], #Fisherman's Recipe
[["Talisman", "Slot998"], [4745.0, 0.0, 0.0, 0.0]],
[["Talisman", "Slot999"], [4875.0, 2816.0, 0.0, 0.0]],
[["Mine99", "mine99_001_clear"], True],
[["Mine99", "mine99_002_clear"], True],
[["Mine99", "mine99_003_clear"], True],
[["Mine99", "mine99_004_clear"], True],
[["Mine99", "mine99_005_clear"], True],
[["Mine99", "mine99_006_clear"], True],
[["Mine99", "mine99_007_clear"], True],
[["Mine99", "mine99_008_clear"], True],
[["Mine99", "mine99_009_clear"], True],
[["Mine99", "mine99_010_clear"], True],
[["Mine99", "mine99_011_clear"], True],
[["Mine99", "mine99_012_clear"], True],
[["Mine99", "mine99_013_clear"], True],
[["Mine99", "mine99_014_clear"], True],
[["Mine99", "mine99_015_clear"], True],
[["Mine99", "mine99_016_clear"], True],
[["Mine99", "mine99_017_clear"], True],
[["Mine99", "mine99_018_clear"], True],
[["Mine99", "mine99_019_clear"], True],
[["Mine99", "mine99_020_clear"], True],
[["Mine99", "mine99_021_clear"], True],
[["Mine99", "mine99_022_clear"], True],
[["Mine99", "mine99_023_clear"], True],
[["Mine99", "mine99_024_clear"], True],
[["Mine99", "mine99_025_clear"], True],
[["Mine99", "mine99_026_clear"], True],
[["Mine99", "mine99_027_clear"], True],
[["Mine99", "mine99_028_clear"], True],
[["Mine99", "mine99_029_clear"], True],
[["Mine99", "mine99_030_clear"], True],
[["Mine99", "mine99_031_clear"], True],
[["Mine99", "mine99_032_clear"], True],
[["Mine99", "mine99_033_clear"], True],
[["Mine99", "mine99_034_clear"], True],
[["Mine99", "mine99_035_clear"], True],
[["Mine99", "mine99_036_clear"], True],
[["Mine99", "mine99_037_clear"], True],
[["Mine99", "mine99_038_clear"], True],
[["Mine99", "mine99_039_clear"], True],
[["Mine99", "mine99_040_clear"], True],
[["Mine99", "mine99_041_clear"], True],
[["Mine99", "mine99_042_clear"], True],
[["Mine99", "mine99_043_clear"], True],
[["Mine99", "mine99_044_clear"], True],
[["Mine99", "mine99_045_clear"], True],
[["Mine99", "mine99_046_clear"], True],
[["Mine99", "mine99_047_clear"], True],
[["Mine99", "mine99_048_clear"], True],
[["Mine99", "mine99_049_clear"], True],
[["Mine99", "mine99_050_clear"], True],
[["Mine99", "mine99_051_clear"], True],
[["Mine99", "mine99_052_clear"], True],
[["Mine99", "mine99_053_clear"], True],
[["Mine99", "mine99_054_clear"], True],
[["Mine99", "mine99_055_clear"], True],
[["Mine99", "mine99_056_clear"], True],
[["Mine99", "mine99_057_clear"], True],
[["Mine99", "mine99_058_clear"], True],
[["Mine99", "mine99_059_clear"], True],
[["Mine99", "mine99_060_clear"], True],
[["Mine99", "mine99_061_clear"], True],
[["Mine99", "mine99_062_clear"], True],
[["Mine99", "mine99_063_clear"], True],
[["Mine99", "mine99_064_clear"], True],
[["Mine99", "mine99_065_clear"], True],
[["Mine99", "mine99_066_clear"], True],
[["Mine99", "mine99_067_clear"], True],
[["Mine99", "mine99_068_clear"], True],
[["Mine99", "mine99_069_clear"], True],
[["Mine99", "mine99_070_clear"], True],
[["Mine99", "mine99_071_clear"], True],
[["Mine99", "mine99_072_clear"], True],
[["Mine99", "mine99_073_clear"], True],
[["Mine99", "mine99_074_clear"], True],
[["Mine99", "mine99_075_clear"], True],
[["Mine99", "mine99_076_clear"], True],
[["Mine99", "mine99_077_clear"], True],
[["Mine99", "mine99_078_clear"], True],
[["Mine99", "mine99_079_clear"], True],
[["Mine99", "mine99_080_clear"], True],
[["Mine99", "mine99_081_clear"], True],
[["Mine99", "mine99_082_clear"], True],
[["Mine99", "mine99_083_clear"], True],
[["Mine99", "mine99_084_clear"], True],
[["Mine99", "mine99_085_clear"], True],
[["Mine99", "mine99_086_clear"], True],
[["Mine99", "mine99_087_clear"], True],
[["Mine99", "mine99_088_clear"], True],
[["Mine99", "mine99_089_clear"], True],
[["Mine99", "mine99_090_clear"], True],
[["Mine99", "mine99_091_clear"], True],
[["Mine99", "mine99_092_clear"], True],
[["Mine99", "mine99_093_clear"], True],
[["Mine99", "mine99_094_clear"], True],
[["Mine99", "mine99_095_clear"], True],
[["Mine99", "mine99_096_clear"], True],
[["Mine99", "mine99_097_clear"], True],
[["Mine99", "mine99_098_clear"], True],
[["Mine99", "mine99_099_clear"], True],
]
elif add_online == "N" or add_online == "n":
add_online = False
else:
print("Error input, use default No")
add_online = False
print("Add max gem? Y/N/C (59990 gems for trophy)")
max_gem = input()
if max_gem == "Y" or max_gem == "y":
max_gem = True
gem_high = 15.0
gem_low = 16974.0
edit_list += [
[["GemInfo", "PreciousGemNum", "Hi"], gem_high],
[["GemInfo", "PreciousGemNum", "Lo"], gem_low],
[["GemInfo", "TotalPreciousGemNum", "Hi"], gem_high],
[["GemInfo", "TotalPreciousGemNum", "Lo"], gem_low]]
elif max_gem == "N" or max_gem == "n":
max_gem = False
elif max_gem == "C" or max_gem == "c":
max_gem = True
gem_high = 0.0
gem_low = 59990.0
edit_list += [
[["GemInfo", "PreciousGemNum", "Hi"], gem_high],
[["GemInfo", "PreciousGemNum", "Lo"], gem_low],
[["GemInfo", "TotalPreciousGemNum", "Hi"], gem_high],
[["GemInfo", "TotalPreciousGemNum", "Lo"], gem_low]]
print(f"Set gems to {gem_low}")
else:
print("Error input, use default No")
max_gem = False
print("Change current outfit? A for export all, 1 for White kat, 2 for Queen Alua, 3 for GR2 Raven, 4 for GR1 Raven, 5 for Sachya, 6 for The Other, 7 for Miner suit. 8 for Cat Suit, 8 for Maid Kat, 10 for Schoolgirl 1.0. Spilt selection with comma")
outfit_changes = []
outfit_nameLookUp = ["kit19", "kit04", "cro01", "cro06", "sac01", "oth01", "kit02", "kit09", "kit14", "kit15"]
outfit_input = input()
if len(outfit_input) != 0:
if outfit_input == 'A' or outfit_input == 'a':
outfit_input = "1,2,3,4,5,6,7,8,9,10"
for num in outfit_input.split(","):
outfit_changes.append(outfit_nameLookUp[int(num)-1])
print(outfit_nameLookUp[int(num)-1])
input("Press Enter to continue...\n")
print("Loading & pharsing savedata...\n")
file = open(file_path, mode='rb+')
data = file.read()
loaded_data = 0
data_set = OrderedDict()
if len(data) > 0x40 and data[0:4] == b'ggdL':
file.seek(0x0c, 0)
numOfData = int.from_bytes(file.read(4), byteorder='little')
while loaded_data < numOfData:
unpack(data_set)
# print()
# print(data_set)
# print()
# print("Complete with %i/%i data" % (loaded_data, numOfData))
# with open(r"%s-bak.txt" % (file_path.split('.')[0]), 'w', encoding='utf-8') as json_file:
# json.dump(data_set, json_file, indent=4, ensure_ascii=False)
with open(r"%s-bak.bin" % file_path.split('.')[0], 'wb') as backup_file:
file.seek(0, 0)
backup_file.write(file.read())
# for edit in edit_list:
else:
print("File Incorrect")
for edit in edit_list:
offset = getOffset(edit[0])
file.seek(offset, 0)
if type(edit[1]) is float:
# print("float")
file.write(struct.pack('<f', edit[1]))
elif type(edit[1]) is bool:
# print("bool")
file.write(struct.pack('<?', edit[1]))
elif type(edit[1]) is list: #Vector
# print("list")
for value in edit[1]:
if type(value) is float:
# print("float")
file.write(struct.pack('<f', value))
# else:
# print("unknown" + str(type(value)))
elif type(edit[1]) is str:
# print("string")
#assume same length
file.write(edit[1].encode('utf-8'))
# else:
# print("unknown" + str(type(edit[1])))
# with open(r"%s.bin" % (file_path.split('.')[0] + "test"), 'wb') as output_file:
# file.seek(0, 0)
# output_file.write(file.read())
#current_outfit_count = int(file_path.split('.')[0][-1:])
# print(hex(offset))
for current_outfit in outfit_changes:
# if (len(current_outfit) > 5):
# print(f'current_outfit length \'{len(current_outfit)}\' is greater than 5')
# break
offset = getOffset(["Player", "Costume"])
file.seek(offset, 0)
file.write(current_outfit.encode('utf-8'))
offset = getOffset(["FreeTimeFlags", "Costume"])
file.seek(offset, 0)
file.write(current_outfit.encode('utf-8'))
file_name = "%s_%s.bin" % (file_path.split('.')[0], current_outfit)
with open(file_name, 'wb') as output_file:
print(file_name)
file.seek(0, 0)
output_file.write(file.read())
# current_outfit_count += 1
file.close()
input("Edit complete. Press Enter to continue...")
if __name__ == '__main__':
main()
#!python3
# Script to transcribe data bin to text GameDatabase. From Null @ Team Alua
import struct
import json
from collections import OrderedDict
import sys
file_path = sys.argv[1]
show_offset = False
show_hash = False
loaded_data = 0
def main():
global file
file = open(file_path, mode='rb')
data = file.read()
global loaded_data
loaded_data = 0
global data_set
data_set = OrderedDict()
if len(data) > 0x40 and data[0:4] == b'ggdL':
file.seek(0x0c, 0)
numOfData = int.from_bytes(file.read(4), byteorder='little')
while loaded_data < numOfData:
unpack(data_set)
print()
print(data_set)
print()
print("Complete with %i/%i data" % (loaded_data, numOfData))
text = transcribe(data_set)
print(text)
print(r"%s.txt" % (file_path.split('.')[0]))
with open(r"%s.txt" % (file_path.split('.')[0]), 'w', encoding='utf-8') as text_file:
text_file.write('\ufeff')
text_file.write(text)
text_file.close()
else:
print("File Incorrect")
def unpack(upstream_data_set):
global loaded_data
loaded_data = loaded_data + 1
currentCursor = file.tell()
print(hex(file.tell()))
file.seek(int.from_bytes(file.read(4), byteorder='little'), 0)
variable_name = file.read(200).split(b'\x00')[0].decode('UTF8') #Use UTF8 because some strings are in Japanese
print(hex(file.tell()))
print(variable_name)
file.seek(currentCursor + 4, 0)
type = int.from_bytes(file.read(4), byteorder='little')
data_location = file.tell()
if type == 0x08: # List
list_length = int.from_bytes(file.read(4), byteorder='little')
name_hash = file.read(4).hex()
data_location = file.tell()
value = {}
for i in range(0, list_length):
unpack(value)
value = OrderedDict(sorted(value.items()))
else:
if type % 0x10 == 0x0b: # String
string_length = int.from_bytes(file.read(4), byteorder='little') - 1
data_location = type // 0x10
file.seek(data_location, 0)
try:
value = file.read(string_length).decode('UTF8')
except:
value = "ERROR EXTRACTING STRING"
file.seek(currentCursor + 0x0c, 0)
elif type == 0x09: # Float
value = struct.unpack('f', file.read(4))[0]
elif type == 0x0C: # Boolean
value = int.from_bytes(file.read(1), byteorder='little') > 0
file.seek(3, 1)
else:
raw_value = int.from_bytes(file.read(4), byteorder='little')
if(raw_value == 0x10): #Vector
data_location = type // 0x10
file.seek(data_location, 0)
value = struct.unpack('ffff', file.read(16))
file.seek(currentCursor + 0x0c, 0)
else:
value = "Unknown type " + hex(type) + " with value " + hex(raw_value)
input("Warring!!! Unknow type!!! %s at %s with value %s" % (hex(type), hex(file.tell()-8), hex(raw_value)))
print()
name_hash = file.read(4).hex()
if variable_name == None:
variable_name = hex(data_location)
else:
if show_hash:
variable_name = variable_name = "%s %s" % (variable_name, name_hash)
if show_offset:
variable_name = variable_name = "%s %s" % (variable_name, hex(data_location))
print(value)
upstream_data_set[variable_name] = value
def transcribe(data_set, level=0):
text = ""
for key in data_set:
text += " " * level
if type(data_set[key]) == OrderedDict:
text += "%s TABLE %d\n" % (key, len(data_set[key]))
text += transcribe(data_set[key], level + 1)
elif type(data_set[key]) == float:
text += "%s FLOAT %.6f(%s)\n" % (key, data_set[key], float2hex(data_set[key]))
elif type(data_set[key]) == bool:
text += "%s BOOLEAN %r\n" % (key, data_set[key])
elif type(data_set[key]) == tuple:
text += "%s VECTOR " % (key)
for i in range(4):
text += "%.6f(%s) " % (data_set[key][i], float2hex(data_set[key][i]))
text += "\n"
return text
def float2hex(float):
hex = ""
ba = bytearray(struct.pack("f", float))
for i in range(3, -1, -1):
byte = format(ba[i], 'x')
if len(byte) == 1:
byte = "0"+byte
hex += byte
return hex
if __name__ == "__main__":
main()

Comments are disabled for this gist.