python gyu2png.py "I:\games\Welcome to a Sexy Open World\Test"
Converting: I:\games\Welcome to a Sexy Open World\Test\example.gyu to I:\games\Welcome to a Sexy Open World\Test\example.png
Failed to convert I:\games\Welcome to a Sexy Open World\Test\example.gyu: 'bytes' object does not support item assignment
```
import os
import struct
import sys
from io import BytesIO
from PIL import Image
import random

class GyuMetaData:
    def __init__(self, flags, compression_mode, obfuscation_seed, depth, width, height, rgb_length, alpha_length, pal_length):
        self.flags = flags
        self.compression_mode = compression_mode
        self.obfuscation_seed = obfuscation_seed
        self.depth = depth
        self.width = width
        self.height = height
        self.rgb_length = rgb_length
        self.alpha_length = alpha_length
        self.pal_length = pal_length

def read_gyu_header(stream):
    signature = stream.read(4)
    if signature != b'GYU\x1A':
        raise ValueError("Invalid GYU file signature.")

    flags, compression_mode, obfuscation_seed = struct.unpack('<HHI', stream.read(8))
    depth, width, height, rgb_length, alpha_length, pal_length = struct.unpack('<IIIIII', stream.read(24))
    
    return GyuMetaData(flags, compression_mode, obfuscation_seed, depth, width, height, rgb_length, alpha_length, pal_length)

def unscramble(data, seed):
    random.seed(seed)
    length = len(data)
    for _ in range(10):
        i1 = random.randint(0, length - 1)
        i2 = random.randint(0, length - 1)
        data[i1], data[i2] = data[i2], data[i1]

def unpack_data(buff, length, out_buff, out_len):
    # Unpack method based on variable-length backreferences
    bit_buffer = 0
    saved_count = 0
    bits = BytesIO(buff)

    def read_bits(bits, count):
        nonlocal bit_buffer, saved_count
        while saved_count < count:
            next_byte = bits.read(1)
            if not next_byte:
                return 0
            bit_buffer = (bit_buffer << 8) | next_byte[0]
            saved_count += 8

        mask = (1 << count) - 1
        val = bit_buffer & mask
        bit_buffer >>= count
        saved_count -= count
        return val

    output_pos = 0
    
    while output_pos < out_len:
        if read_bits(bits, 1) == 1:
            out_buff[output_pos] = bits.read(1)[0]
            output_pos += 1
        else:
            n = 0
            p = 0
            
            if read_bits(bits, 1) == 1:
                n = 0xFFFF0000 | (bits.read(1)[0] << 8) | bits.read(1)[0]
                p = (n >> 3) | 0xFFFFE000
                n &= 7
                
                if n:
                    n += 1
                else:
                    n = bits.read(1)[0]
                    if n == 0:
                        break

            else:
                n = read_bits(bits, 2) + 1
                p = bits.read(1)[0] | 0xFFFFFF00

            n += 1
            while n > 0:
                out_buff[output_pos] = out_buff[output_pos + p]
                output_pos += 1
                n -= 1

def convert_gyu_to_png(input_path, output_path):
    with open(input_path, 'rb') as f:
        header = read_gyu_header(f)

        # Read the palette if it exists
        palette = []
        if header.pal_length > 0:
            palette = [struct.unpack('BBB', f.read(3)) for _ in range(header.pal_length)]

        rgb_buff = bytes(f.read(header.rgb_length))

        if header.obfuscation_seed:
            unscramble(rgb_buff, header.obfuscation_seed)

        # Determine output buffer size
        depth = header.depth // 8
        out_stride = (header.width * depth + 3) & ~3
        out_len = header.height * out_stride
        out_buff = bytearray(out_len)

        if header.compression_mode == 0x0800:  # GYU compression
            unpack_data(rgb_buff[4:], header.rgb_length - 4, out_buff, out_len)
        else:
            raise NotImplementedError("Unsupported compression method")

        # Prepare image data
        image = Image.new('RGBA', (header.width, header.height))
        for y in range(header.height):
            for x in range(header.width):
                if header.depth == 8:  # Indexed color
                    index = out_buff[y * out_stride + x]
                    r, g, b = palette[index]
                elif header.depth == 24:  # RGB
                    r = out_buff[(y * out_stride) + (x * 3) + 0]
                    g = out_buff[(y * out_stride) + (x * 3) + 1]
                    b = out_buff[(y * out_stride) + (x * 3) + 2]
                elif header.depth == 32:  # RGBA
                    r = out_buff[(y * out_stride) + (x * 4) + 0]
                    g = out_buff[(y * out_stride) + (x * 4) + 1]
                    b = out_buff[(y * out_stride) + (x * 4) + 2]
                else:
                    raise ValueError("Unsupported depth: {}".format(depth))

                # Set pixel color in image object
                image.putpixel((x, y), (r, g, b, 255))

        # Save the image to output
        image.save(output_path, 'PNG')

def convert_all_gyu_in_folder(input_folder):
    for subdir, _, files in os.walk(input_folder):
        for file in files:
            if file.lower().endswith('.gyu'):
                input_file = os.path.join(subdir, file)
                output_file = os.path.join(subdir, f"{os.path.splitext(file)[0]}.png")
                print(f"Converting: {input_file} to {output_file}")
                try:
                    convert_gyu_to_png(input_file, output_file)
                except Exception as e:
                    print(f"Failed to convert {input_file}: {e}")

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python gyu2png.py <input_folder>")
    else:
        input_folder = sys.argv[1]
        convert_all_gyu_in_folder(input_folder)