EXTRACTING SPRITES PNG sprite sheets are stored in the directories: "Animations" (characters, in-game sprites) "Assets" (background and objects) "Images" (UI, cutscenes, etc) animation frame data is stored in .PLIST XML files Key "frame" shows the X and Y coordinates, followed by the width and height of the crop. "Offset" is how many transparent pixels to add to the sides of the crop. If "rotated" is set to "true", reverse the width and height values in "frame", and rotate the crop counter-clockwise by 90 degrees. "sourceColorRect" and "sourceSize" can be ignored. the game assets were generated using TexturePacker (https://www.codeandweb.com/texturepacker), though as far as I can tell while it can load a sheet and isolate each frame, you can't feed it the PLIST or other data to do all the extra work for you. gotta format the PLIST to TXT...! (getting Python to cooperate with the PLIST files was a nightmare, so i had to cut them down into five lines of data per frame remove linebreaks and spaces remove - remove remove sourceSize{800,800} replace \n replace \n replace {false}\n replace {true}\n replace \n replace {{ { replace }} } import os import re from PIL import Image def parse_txt(file_path): with open(file_path, 'r') as f: lines = [line.strip() for line in f.readlines() if line.strip()] crops = [] i = 0 while i < len(lines): output_path = lines[i] frame_match = re.search(r'frame{(\d+),(\d+)},{(\d+),(\d+)}', lines[i + 1]) offset_match = re.search(r'offset{(\d+),(\d+)}', lines[i + 2]) rotated_match = re.search(r'rotated{(true|false)}', lines[i + 3]) if frame_match and offset_match and rotated_match: x, y, w, h = map(int, frame_match.groups()) offset_x, offset_y = map(int, offset_match.groups()) rotated = rotated_match.group(1) == 'true' if rotated: w, h = h, w # Swap width and height for rotated images crops.append({ "output_path": output_path, "x": x, "y": y, "w": w, "h": h, "offset_x": offset_x, "offset_y": offset_y, "rotated": rotated }) i += 5 # Move to the next crop section return crops def extract_images(source_image, crops): img = Image.open(source_image) for crop in crops: cropped_img = img.crop((crop["x"], crop["y"], crop["x"] + crop["w"], crop["y"] + crop["h"])) # Apply transparent offset new_w = crop["w"] + crop["offset_x"] new_h = crop["h"] + crop["offset_y"] new_img = Image.new("RGBA", (new_w, new_h), (0, 0, 0, 0)) new_img.paste(cropped_img, (crop["offset_x"], crop["offset_y"])) # Rotate if necessary if crop["rotated"]: new_img = new_img.rotate(-90, expand=True) # Create output directory if necessary output_dir = os.path.dirname(crop["output_path"]) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir) # Save the image new_img.save(crop["output_path"] + ".png") if __name__ == "__main__": txt_file = "hero1_animations_1.txt" # Path to your TXT file source_image = "hero1_animations_1.png" # Path to your source PNG image crops = parse_txt(txt_file) extract_images(source_image, crops) print("Image extraction complete!") RECOLOURING SPRITES enemies and other objects have areas coloured in RGB to designate they're for recolouring (that is, colours that have 0 in two channels) "Scripts/Data/enemies.lua" has palette data under "data:setRedColor", etc I cannot wrap my head around the RGB separation/decompose functions in GIMP/Paint.net -- where's my alpha data??? -- so i used another dodgy AI python script to separate them from PIL import Image def isolate_color(image_path, output_prefix): img = Image.open(image_path).convert("RGBA") pixels = img.load() width, height = img.size red_img = Image.new("RGBA", (width, height), (0, 0, 0, 0)) green_img = Image.new("RGBA", (width, height), (0, 0, 0, 0)) blue_img = Image.new("RGBA", (width, height), (0, 0, 0, 0)) red_pixels = red_img.load() green_pixels = green_img.load() blue_pixels = blue_img.load() for x in range(width): for y in range(height): r, g, b, a = pixels[x, y] if r > 0 and g == 0 and b == 0: # Pure red red_pixels[x, y] = (r, g, b, a) elif g > 0 and r == 0 and b == 0: # Pure green green_pixels[x, y] = (r, g, b, a) elif b > 0 and r == 0 and g == 0: # Pure blue blue_pixels[x, y] = (r, g, b, a) red_img.save(f"{output_prefix}_red.png") green_img.save(f"{output_prefix}_green.png") blue_img.save(f"{output_prefix}_blue.png") # Example usage image_path = "biker_animations.png" output_prefix = "output" isolate_color(image_path, output_prefix) after that, open it in Paint.net Adjustments > Hue & Saturation > set Saturation to 0 Adjustments > Levels, set top Input value to 128 new layer with appropriate colour, set to Multiply it might not be 1:1 with the game but it'll do in a pinch!