import sys
import os
import struct
import shutil

if sys.stdout.encoding.lower() != 'utf-8':
    sys.stdout.reconfigure(encoding='utf-8')

def crc16_xmodem(data: bytes) -> int:
    crc = 0x0000
    for byte in data:
        crc ^= (byte << 8)
        for _ in range(8):
            if crc & 0x8000:
                crc = (crc << 1) ^ 0x1021
            else:
                crc <<= 1
            crc &= 0xFFFF
    return crc

def find_wildcard_all(data: bytearray, pattern) -> list:
    """Find all positions matching pattern. None in pattern = wildcard byte."""
    plen = len(pattern)
    positions = []
    for i in range(len(data) - plen + 1):
        if all(p is None or data[i + j] == p for j, p in enumerate(pattern)):
            positions.append(i)
    return positions

def apply_wildcard_patch(data: bytearray, search, replace: bytes) -> int:
    """Replace all matches of search pattern with replace bytes. Returns match count."""
    assert len(search) == len(replace), \
        f"Pattern length {len(search)} != replacement length {len(replace)}"
    positions = find_wildcard_all(data, search)
    for pos in reversed(positions):
        data[pos:pos + len(replace)] = replace
    return len(positions)

# ============================================================
# Pattern 1: divr-based (AudioEffectPcmDelayInit / Apply / Apply24)
# Original C: samples = param.value * sample_rate / 1000
# Patched  C: samples = param.value   (sample_rate==44100, div cancelled)
# JAL not included — target offset varies between firmware versions
# ============================================================

# AudioEffectPcmDelayInit — 32 bytes before jal pcm_delay_init24
# Original: lhsi r2 / lwi.gp r3(sample_rate) / sbi / mul33 / movi / divr / lwi r4 / zeh33 / lhsi r3
# Patched:  sbi / nop×4 / lhi r2 / lhsi r3 / lwi r4
INIT_1000 = [
    0x22, 0x20, 0x00, 0x8f,             # lhsi $r2,[$r0+#0x11e]
    0x3c, 0x3d, None, None,             # lwi.gp $r3,[+#-70128]   (sample_rate)
    0x10, 0x10, 0x01, 0x23,             # sbi $r1,[$r0+#0x123]
    0xfe, 0xd4,                         # mul33 $r3,$r2
    0x44, 0x20, 0x03, 0xe8,             # movi $r2,#0x3e8
    0x40, 0x21, 0x88, 0x57,             # divr $r2,$r2,$r3,$r2
    0x04, 0x40, 0x00, 0x49,             # lwi $r4,[$r0+#0x124]
    0x96, 0x91,                         # zeh33 $r2,$r2
    0x22, 0x30, 0x00, 0x90,             # lhsi $r3,[$r0+#0x120]
]
INIT_NOP = bytes([
    0x10, 0x10, 0x01, 0x23,             # sbi $r1,[$r0+#0x123]
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x02, 0x20, 0x00, 0x8f,             # lhi $r2,[$r0+#0x11e]
    0x22, 0x30, 0x00, 0x90,             # lhsi $r3,[$r0+#0x120]
    0x04, 0x40, 0x00, 0x49,             # lwi $r4,[$r0+#0x124]
])

# AudioEffectPcmDelayApply — 20 bytes before jal pcm_delay_apply16
# 3c 5d ?? ?? = lwi.gp $r5 (GP offset varies per firmware) — wildcard, replaced with nop
# Original: lwi.gp r5 / lhsi r4 / mul33 / movi / divr / zeh33
# Patched:  nop×4 / lhi r4
APPLY_1000 = [
    0x3c, 0x5d, None, None,             # lwi.gp $r5,[gp+??]   (sample_rate, offset varies)
    0x22, 0x40, 0x00, 0x8e,             # lhsi $r4,[$r0+#0x11c]
    0xff, 0x64,                         # mul33 $r5,$r4
    0x44, 0x40, 0x03, 0xe8,             # movi $r4,#0x3e8
    0x40, 0x42, 0x90, 0x97,             # divr $r4,$r4,$r5,$r4
    0x97, 0x21,                         # zeh33 $r4,$r4
]
APPLY_NOP = bytes([
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x02, 0x40, 0x00, 0x8e,             # lhi $r4,[$r0+#0x11c]
])

# AudioEffectPcmDelayApply24 — 18 bytes before jal pcm_delay_apply24
# 3c 5d ?? ?? = lwi.gp $r5 (GP offset varies per firmware) — wildcard, replaced with nop
# Original: lwi.gp r5 / lhsi r4 / mul33 / movi / divr  (no zeh33)
# Patched:  nop×3 / nop16 / lhsi r4
APPLY24_1000 = [
    0x3c, 0x5d, None, None,             # lwi.gp $r5,[gp+??]   (sample_rate, offset varies)
    0x22, 0x40, 0x00, 0x8e,             # lhsi $r4,[$r0+#0x11c]
    0xff, 0x64,                         # mul33 $r5,$r4
    0x44, 0x40, 0x03, 0xe8,             # movi $r4,#0x3e8
    0x40, 0x42, 0x90, 0x97,             # divr $r4,$r4,$r5,$r4
]
APPLY24_NOP = bytes([
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x92, 0x00,                         # nop16
    0x22, 0x40, 0x00, 0x8e,             # lhsi $r4,[$r0+#0x11c]
])

# ============================================================
# Pattern 2: smmul-based (AudioEffectParsePackage - PcmDelay block)
# ============================================================

# /1000 smmul block (30 bytes) — confirmed from real firmware
# 3c 0d ?? ?? = lwi.gp variant, offset varies per firmware — wildcard
SMMUL_1000 = [
    0x22, 0x26, 0x80, 0x8f,             # [prefix]
    0x3c, 0x0d, None, None,             # [prefix, GP offset varies]
    0xfe, 0x84,                         # mul33 $r2,$r0
    0x46, 0x11, 0x06, 0x24,             # sethi $r1,#0x10624
    0x50, 0x10, 0x8d, 0xd3,             # addi  $r1,#0xdd3  => 0x10624DD3
    0x42, 0x01, 0x04, 0xb8,             # smmul $r0,$r2,$r1
    0x90, 0x5f,                         # [correction]
    0xfe, 0x56,                         # [correction]
    0x88, 0x01,                         # add45 $r0,$r1
    0x92, 0x06,                         # srli45 $r0,#0x6
]

# /44100 smmul replacement (30 bytes) — NOP-padded, equal length
SMMUL_44100 = bytes([
    0x22, 0x06, 0x80, 0x8f,             # [modified prefix]
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x40, 0x00, 0x00, 0x09,             # nop
    0x92, 0x00,                         # nop16
])

# All patch entries: (name, search, replace)
# search can be bytes (exact) or list with None = wildcard byte
PATCHES = [
    ("PcmDelayInit (divr)",    INIT_1000,    INIT_NOP),
    ("PcmDelayApply (divr)",   APPLY_1000,   APPLY_NOP),
    ("PcmDelayApply24 (divr)", APPLY24_1000, APPLY24_NOP),
    ("ParsePackage (smmul)",   SMMUL_1000,   SMMUL_44100),
]

def patch_file(file_path: str):
    if not os.path.exists(file_path):
        print(f"Ошибка: Файл '{file_path}' не найден.")
        return

    with open(file_path, 'rb') as f:
        full_data = bytearray(f.read())

    if len(full_data) < 4:
        print("Ошибка: Файл слишком мал.")
        return

    print("Режим: ПАТЧ")

    total_patches = 0
    for name, search, replace in PATCHES:
        count = apply_wildcard_patch(full_data, search, replace)
        if count > 0:
            print(f"  {name}: найдено {count} шт.")
            total_patches += count
        else:
            print(f"  {name}: не найдено")

    if total_patches == 0:
        print(">>> СТАТУС: Паттерны не найдены. Файл уже изменен или не поддерживается.")
        return

    # Резервная копия (для отката используйте .bak файл)
    backup_path = file_path + ".bak"
    if not os.path.exists(backup_path):
        shutil.copy2(file_path, backup_path)
        print(f"Резервная копия: {backup_path}")
    else:
        print(f"Резервная копия уже существует: {backup_path}")

    # Пересчитываем CRC
    new_crc = crc16_xmodem(full_data[:-4])
    packed_crc = struct.pack('<H', new_crc)
    full_data[-4] = packed_crc[0]
    full_data[-3] = packed_crc[1]
    print(f"Новый CRC: 0x{new_crc:04X}")

    with open(file_path, 'wb') as f:
        f.write(full_data)

    print(f">>> СТАТУС: УСПЕШНО! {total_patches} патч(ей) применено.")
    print(f"    Для отката: скопируйте {backup_path} обратно в {file_path}")

def main():
    if len(sys.argv) < 2:
        print(f"Использование: python {os.path.basename(__file__)} <имя_файла.mva>")
        print()
        print("Патчит в функциях:")
        print("  - AudioEffectPcmDelayInit    (divr pattern)")
        print("  - AudioEffectPcmDelayApply   (divr pattern)")
        print("  - AudioEffectPcmDelayApply24 (divr pattern)")
        print("  - AudioEffectParsePackage    (smmul pattern)")
        print()
        print("Для отката: восстановите оригинальный файл из резервной копии .bak")
        return

    patch_file(sys.argv[1])

if __name__ == "__main__":
    main()
