Chủ Nhật, 3 tháng 5, 2026

Giới thiệu bộ lập trình giá rẻ CH341A và phần mềm NeoProgrammer 2.2.0.10 “quốc dân” cho dân điện tử

Trong lĩnh vực sửa chữa phần cứng, lập trình firmware hay nghiên cứu hệ thống nhúng, cái tên CH341A gần như đã trở thành “huyền thoại” trong phân khúc giá rẻ. Chỉ với chi phí rất thấp, người dùng đã có thể sở hữu một thiết bị đủ khả năng đọc, ghi và phục hồi dữ liệu cho nhiều loại chip nhớ phổ biến. Vậy CH341A là gì, hoạt động ra sao, và tại sao nó lại được sử dụng rộng rãi đến vậy?

Có thể mua trên app "con cá" hoặc tự làm, có link ở dưới


 

1. CH341A là gì?

CH341A là một bộ lập trình (programmer) sử dụng giao tiếp USB, được thiết kế để làm việc với các loại bộ nhớ không bay hơi như EEPROM và Flash. Về bản chất, nó là một thiết bị trung gian giúp kết nối chip nhớ với máy tính, cho phép người dùng thực hiện các thao tác như đọc (read), ghi (write), xóa (erase) hoặc sao lưu firmware.

Thiết bị này thường được sử dụng với các dòng chip phổ biến như:

  • EEPROM dòng 24Cxx (I2C)
  • SPI Flash dòng 25xx (Winbond, MXIC, SST…)
  • Một số chip BIOS trên mainboard máy tính

Nhờ khả năng này, CH341A trở thành công cụ quen thuộc trong các công việc như:

  • Sửa lỗi BIOS máy tính
  • Dump firmware thiết bị (router, TV, đầu thu…)
  • Phục hồi thiết bị bị lỗi firmware
  • Nghiên cứu bảo mật phần cứng

2. Thiết kế phần cứng và nguyên lý hoạt động

CH341A thường có dạng một USB dongle nhỏ gọn, kích thước chỉ vài cm, rất tiện mang theo. Bên trong là IC CH341A – một chip giao tiếp đa năng có thể hỗ trợ UART, SPI và I2C. 

Nguyên lý hoạt động khá đơn giản:

  1. Người dùng kết nối CH341A với máy tính qua USB
  2. Gắn chip cần đọc/ghi vào socket hoặc dùng kẹp SOP8 (SOIC clip)
  3. Sử dụng phần mềm để giao tiếp và thao tác với chip

Thiết bị sẽ đóng vai trò cầu nối, chuyển đổi dữ liệu từ USB sang giao thức SPI/I2C để giao tiếp trực tiếp với chip nhớ. 

Một điểm đáng chú ý là CH341A loại board mạch màu đen chỉ hỗ trợ nhiều mức điện áp 5V, như vậy sẽ rất nguy hiểm với những chip hoạt động 3.3V (phải mod lại mạch), 1.8V chắc chắn phải dùng với adapter để giúp tương thích với nhiều loại IC. 

3. Tính năng nổi bật

Mặc dù có giá rất rẻ, CH341A vẫn sở hữu nhiều tính năng đáng chú ý:

✔ Hỗ trợ nhiều loại chip

CH341A tương thích với hầu hết các chip EEPROM và SPI Flash phổ biến trên thị trường, đặc biệt là dòng 24 và 25 series. 

✔ Kết nối USB tiện lợi

Chỉ cần cắm vào máy tính là có thể sử dụng, không cần nguồn ngoài phức tạp.

✔ Đa chức năng

Thiết bị cho phép:
  • Đọc dữ liệu từ chip
  • Ghi firmware mới
  • Xóa chip
  • So sánh dữ liệu (verify)

✔ Tương thích phần mềm rộng

CH341A có thể sử dụng với nhiều phần mềm khác nhau như:

  • Flashrom (Linux)
  • IMSProg
  • AsProgrammer
  • CH341A Programmer
  • NeoProgrammer
  • CH341Programmer
  • avrdudess
  • Postal
  • AVR Full mode programmer
  • SiberiaProg-CH341A
  • PinTester
  • Colibri
  • SNANDer
  • CH341ACH347 Programmer

✔ Giá thành cực thấp

Đây chính là điểm mạnh lớn nhất. CH341A thường chỉ có giá vài đô la nhưng vẫn đáp ứng được nhu cầu cơ bản của cả người mới lẫn kỹ thuật viên.

4. Ứng dụng thực tế

CH341A được sử dụng trong rất nhiều tình huống thực tế:

🔧 Sửa lỗi BIOS máy tính

Khi BIOS bị lỗi (ví dụ update sai), máy không thể khởi động. CH341A cho phép nạp lại firmware trực tiếp vào chip BIOS mà không cần boot hệ thống.

📺 Sửa thiết bị điện tử

TV, router, đầu thu kỹ thuật số… thường lưu firmware trong SPI Flash. Khi firmware bị lỗi, CH341A giúp nạp lại dữ liệu để khôi phục thiết bị.

🔍 Dump firmware và reverse engineering

Trong lĩnh vực bảo mật, CH341A được dùng để đọc firmware nhằm phân tích hệ thống, tìm lỗ hổng hoặc nghiên cứu thiết bị.

🧪 Học tập và nghiên cứu

Với giá rẻ, CH341A là lựa chọn phổ biến cho sinh viên và người mới học về embedded systems.

5. Ưu điểm

CH341A được ưa chuộng không phải ngẫu nhiên, mà nhờ những ưu điểm rõ ràng:

  • Giá cực rẻ, phù hợp với mọi đối tượng
  • Dễ sử dụng, chỉ cần phần mềm đơn giản
  • Nhỏ gọn, tiện lợi
  • Hỗ trợ đa dạng chip
  • Cộng đồng sử dụng lớn, dễ tìm tài liệu

Chính vì vậy, nó thường được gọi là “tool quốc dân” trong giới sửa chữa phần cứng.

6. Nhược điểm và lưu ý quan trọng

Tuy nhiên, CH341A không phải là hoàn hảo. Người dùng cần lưu ý một số điểm:

⚠ Vấn đề điện áp

Một số phiên bản CH341A (đặc biệt bản đen phổ biến) có thể xuất mức điện áp 5V trên chân dữ liệu, gây nguy hiểm cho chip 3.3V và có thể làm hỏng IC.

⚠ Cần adapter cho chip 1.8V

Nhiều chip hiện đại (đặc biệt BIOS laptop) dùng 1.8V, nên cần adapter riêng.

⚠ Tốc độ không cao

So với các programmer chuyên nghiệp, CH341A có tốc độ đọc/ghi khá chậm.

⚠ Phụ thuộc phần mềm

Một số chip mới không được hỗ trợ tốt, cần chọn đúng phần mềm hoặc cấu hình thủ công.

7. So sánh với các giải pháp khác

So với các programmer cao cấp như TL866 hoặc Bus Pirate, CH341A có thể thua về:

  • Độ ổn định
  • Tốc độ
  • Khả năng hỗ trợ chip mới

Tuy nhiên, xét về giá/hiệu năng, CH341A gần như không có đối thủ trong phân khúc giá rẻ.

NeoProgrammer 2.2.0.10 – phần mềm “quốc dân” cho CH341A

Trong hệ sinh thái các công cụ dành cho bộ lập trình CH341A, bên cạnh những phần mềm quen thuộc như CH341A Programmer hay AsProgrammer, NeoProgrammer 2.2.0.10 nổi lên như một lựa chọn mạnh mẽ, ổn định và được cộng đồng kỹ thuật viên đánh giá rất cao. Đây là phần mềm được cải tiến từ AsProgrammer, với nhiều nâng cấp về khả năng hỗ trợ chip, giao diện và độ ổn định khi làm việc thực tế. 

1. NeoProgrammer là gì?

NeoProgrammer là phần mềm chuyên dùng để đọc, ghi và quản lý dữ liệu trên các chip nhớ khi sử dụng programmer như CH341A. Nó đóng vai trò là “bộ não” điều khiển toàn bộ quá trình giao tiếp giữa máy tính và IC.

Phần mềm này hỗ trợ nhiều loại bộ nhớ như:

  • SPI NOR Flash
  • SPI NAND Flash
  • EEPROM (24Cxx, 25xx, 93Cxx…)
  • FRAM và một số vi điều khiển (MCU)

Nhờ khả năng này, NeoProgrammer được sử dụng rộng rãi trong:

  • Sửa chữa BIOS máy tính
  • Dump firmware thiết bị điện tử
  • Nạp firmware cho router, TV box
  • Nghiên cứu phần cứng và reverse engineering

2. Nguồn gốc và sự phát triển

NeoProgrammer thực chất là một phiên bản nâng cấp từ AsProgrammer, một dự án mã nguồn mở phổ biến. tác giả  TTAV134 tại diễn đàn https://4pda.to/forum/index.php?showuser=9359810 cải tiến thêm nhiều tính năng và độ ổn định. 

Phiên bản 2.2.0.10 được xem là một trong những bản ổn định nhất, với các cải tiến:

Nhờ đó, NeoProgrammer dần thay thế các phần mềm cũ vốn kém ổn định hoặc hỗ trợ hạn chế.

3. Giao diện và cách sử dụng

Một trong những điểm mạnh lớn của NeoProgrammer là giao diện trực quan, dễ sử dụng hơn so với nhiều tool khác.

Quy trình sử dụng cơ bản:

  1. Kết nối CH341A với máy tính
  2. Gắn chip vào socket hoặc kẹp SOP8
  3. Mở NeoProgrammer
  4. Nhấn Detect IC để nhận diện chip hoặc chọn thủ công
  5. Thực hiện Read / Write / Erase / Verify

Ngoài ra, phần mềm còn tích hợp sơ đồ chân (pinout) trực quan, giúp người dùng tránh cắm sai chip – một lỗi rất phổ biến với người mới.

4. Tính năng nổi bật

✔ Hỗ trợ số lượng chip lớn

NeoProgrammer mặc định ban đầu đã hỗ trợ hơn 1600 loại IC khác nhau từ những đóng góp từ cộng đồng, thời điểm hiện tại hỗ trợ là 2110 chip, bao gồm cả các dòng chip mới. 

✔ Hỗ trợ nhiều định dạng firmware

Phần mềm cho phép làm việc với các file:

  • BIN
  • HEX
  • ELF

✔ Tự động kiểm tra dữ liệu (Verify)

Sau khi ghi firmware, phần mềm sẽ tự động so sánh dữ liệu để đảm bảo không bị lỗi.

✔ Tương thích tốt với CH341A

NeoProgrammer được tối ưu đặc biệt cho CH341A (cả bản đen và bản xanh), giúp giảm lỗi nhận chip và tăng độ ổn định. 

✔ Portable – không cần cài đặt

Chỉ cần giải nén và chạy, không cần cài đặt phức tạp. 

✔ Công cụ chẩn đoán và sửa lỗi

Một số bản tích hợp thêm:

  • Công cụ test LED (set-top box)
  • Công cụ chẩn đoán firmware
  • Hỗ trợ sửa lỗi thiết bị

5. Ứng dụng thực tế

NeoProgrammer thường được dùng trong các tình huống:

🔧 Sửa BIOS (cứu máy “brick”)

Đây là ứng dụng phổ biến nhất. Khi BIOS lỗi, NeoProgrammer + CH341A có thể:

  • Dump BIOS cũ
  • Nạp BIOS mới
  • Phục hồi mainboard

📺 Sửa thiết bị điện tử

Router, TV, đầu thu kỹ thuật số… thường sử dụng SPI Flash, có thể nạp lại firmware bằng NeoProgrammer.

🔍 Nghiên cứu bảo mật

Các chuyên gia bảo mật sử dụng NeoProgrammer để:

  • Dump firmware
  • Phân tích hệ thống
  • Tìm lỗ hổng

🧪 Học tập

Đây là công cụ rất phù hợp cho sinh viên học embedded systems.

6. Ưu điểm

NeoProgrammer 2.2.0.10 được ưa chuộng nhờ:

  • Miễn phí hoàn toàn
  • Giao diện dễ dùng
  • Hỗ trợ nhiều chip hơn phần mềm gốc
  • Hoạt động ổn định với CH341A
  • Cộng đồng sử dụng lớn

Ngoài ra, nhiều người đánh giá đây là “cứu tinh” khi cần phục hồi thiết bị bị lỗi firmware (brick). 

7. Nhược điểm và hạn chế

Tuy mạnh mẽ, NeoProgrammer vẫn có một số điểm cần lưu ý:

⚠ Không phải chip nào cũng hỗ trợ hoàn hảo

Dù danh sách chip lớn, vẫn có những IC mới hoặc đặc biệt không nhận diện được.

⚠ Phụ thuộc phần cứng CH341A

Nếu CH341A gặp lỗi (điện áp sai, tiếp xúc kém), phần mềm cũng không thể hoạt động đúng.

⚠ Một số lỗi “IC not responding”

Đây là lỗi phổ biến, thường do:

  • Kẹp chip không chắc
  • Sai điện áp
  • Chip đang bị khóa

⚠ Không có tài liệu chính thức đầy đủ

Phần lớn người dùng phải học qua forum và cộng đồng.

8. So sánh với các phần mềm khác

Phần mềmƯu điểmNhược điểm
CH341A Programmer        Đơn giản        Ít chip hỗ trợ
AsProgrammer        Mã nguồn mở        Giao diện khó dùng
NeoProgrammer        Nhiều chip, ổn định        Ít tài liệu

Có thể nói NeoProgrammer là sự cân bằng tốt nhất giữa tính năng – dễ dùng – độ ổn định.

9. Kết luận

CH341A là một công cụ nhỏ gọn nhưng cực kỳ hữu ích trong lĩnh vực điện tử và sửa chữa phần cứng. Với khả năng đọc/ghi EEPROM và Flash, hỗ trợ nhiều loại chip và giá thành thấp, nó đã trở thành lựa chọn hàng đầu cho cả người mới bắt đầu lẫn kỹ thuật viên chuyên nghiệp.

NeoProgrammer 2.2.0.10 là một trong những phần mềm tốt nhất dành cho CH341A hiện nay. Với khả năng hỗ trợ hàng nghìn loại chip, giao diện thân thiện và hiệu năng ổn định, nó đã trở thành công cụ không thể thiếu đối với:

  • Kỹ thuật viên sửa chữa phần cứng
  • Người nghiên cứu firmware
  • Sinh viên và maker

Nếu CH341A là “phần cứng quốc dân”, thì NeoProgrammer chính là “phần mềm quốc dân” đi kèm. Khi kết hợp hai công cụ này, người dùng có thể thực hiện từ những thao tác cơ bản như đọc EEPROM cho đến các công việc nâng cao như phục hồi BIOS hay phân tích firmware một cách hiệu quả.

Nếu bạn đang bước chân vào lĩnh vực phần cứng, thì combo NeoProgrammer + CH341A gần như là một thiết bị “phải có” trong bộ công cụ của mình.


Nguồn tham khảo:

https://4pda.to/forum/index.php?showtopic=884713

https://oshwhub.com/misslee/ch341a-nextprogrammer

https://www.right.com.cn/forum/thread-8290935-1-1.html

 

Link tải phần mềm dự phòng:

Bonus: Tôi thích chương trình NeoProgrammer vì tính ổn định và hiện được cộng đồng anh em sử dụng rất nhiều, nhưng có 1 điểm là muốn thêm chip mới hơi có vẻ rườm rà và chiplist.dat hiện tại được nén + mã hóa nên không thể trích xuất toàn bộ nội dung của nó ra file xml, thực chất việc này là vô ích vì có thể tự thêm nó vào trong file Import.xml.....nhưng vì sở thích cá nhân nên thích làm chuyện vô bổ.

Dưới đây là cách tôi đã nhờ người bạn @tongo0448 (discord) sử dụng Reverse engineering với IDA MCP để trích xuất chiplist từ Memory:

Một lần nữa cảm ơn đặc biệt tới @tongo0448 đã giúp tôi.

import ctypes
import os
import struct
import sys
from ctypes import wintypes

APP_DIR = r"CHANGE_DIR_PATH\NeoProgrammer V2.2.0.10"
APP_EXE = os.path.join(APP_DIR, "NeoProgrammer.exe")
OUT_XML = os.path.join(r"CHANGE_DIR_PATH", "chiplist.xml")

DEBUG_ONLY_THIS_PROCESS = 0x00000002
CREATE_NEW_CONSOLE = 0x00000010
DBG_CONTINUE = 0x00010002
DBG_EXCEPTION_NOT_HANDLED = 0x80010001
EXCEPTION_DEBUG_EVENT = 1
CREATE_PROCESS_DEBUG_EVENT = 3
EXIT_PROCESS_DEBUG_EVENT = 5
EXCEPTION_BREAKPOINT = 0x80000003
STATUS_WX86_BREAKPOINT = 0x4000001F
EXCEPTION_SINGLE_STEP = 0x80000004
CONTEXT_FULL = 0x00010007
MEM_COMMIT = 0x1000
PAGE_GUARD = 0x100
PAGE_NOACCESS = 0x01

RVA_BPS = {
    0x27C70: "load chiplist database",
    0x5B87A: "before stream transform",
    0x5B87F: "after stream transform",
    0x27D1C: "XML parser load",
}


class STARTUPINFO(ctypes.Structure):
    _fields_ = [
        ("cb", wintypes.DWORD),
        ("lpReserved", wintypes.LPWSTR),
        ("lpDesktop", wintypes.LPWSTR),
        ("lpTitle", wintypes.LPWSTR),
        ("dwX", wintypes.DWORD),
        ("dwY", wintypes.DWORD),
        ("dwXSize", wintypes.DWORD),
        ("dwYSize", wintypes.DWORD),
        ("dwXCountChars", wintypes.DWORD),
        ("dwYCountChars", wintypes.DWORD),
        ("dwFillAttribute", wintypes.DWORD),
        ("dwFlags", wintypes.DWORD),
        ("wShowWindow", wintypes.WORD),
        ("cbReserved2", wintypes.WORD),
        ("lpReserved2", ctypes.c_void_p),
        ("hStdInput", wintypes.HANDLE),
        ("hStdOutput", wintypes.HANDLE),
        ("hStdError", wintypes.HANDLE),
    ]


class PROCESS_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("hProcess", wintypes.HANDLE),
        ("hThread", wintypes.HANDLE),
        ("dwProcessId", wintypes.DWORD),
        ("dwThreadId", wintypes.DWORD),
    ]


class EXCEPTION_RECORD(ctypes.Structure):
    _fields_ = [
        ("ExceptionCode", wintypes.DWORD),
        ("ExceptionFlags", wintypes.DWORD),
        ("ExceptionRecord", ctypes.c_void_p),
        ("ExceptionAddress", ctypes.c_void_p),
        ("NumberParameters", wintypes.DWORD),
        ("ExceptionInformation", ctypes.c_size_t * 15),
    ]


class EXCEPTION_DEBUG_INFO(ctypes.Structure):
    _fields_ = [
        ("ExceptionRecord", EXCEPTION_RECORD),
        ("dwFirstChance", wintypes.DWORD),
    ]


class CREATE_PROCESS_DEBUG_INFO(ctypes.Structure):
    _fields_ = [
        ("hFile", wintypes.HANDLE),
        ("hProcess", wintypes.HANDLE),
        ("hThread", wintypes.HANDLE),
        ("lpBaseOfImage", ctypes.c_void_p),
        ("dwDebugInfoFileOffset", wintypes.DWORD),
        ("nDebugInfoSize", wintypes.DWORD),
        ("lpThreadLocalBase", ctypes.c_void_p),
        ("lpStartAddress", ctypes.c_void_p),
        ("lpImageName", ctypes.c_void_p),
        ("fUnicode", wintypes.WORD),
    ]


class EXIT_PROCESS_DEBUG_INFO(ctypes.Structure):
    _fields_ = [("dwExitCode", wintypes.DWORD)]


class DEBUG_EVENT_UNION(ctypes.Union):
    _fields_ = [
        ("Exception", EXCEPTION_DEBUG_INFO),
        ("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO),
        ("ExitProcess", EXIT_PROCESS_DEBUG_INFO),
        ("raw", ctypes.c_byte * 160),
    ]


class DEBUG_EVENT(ctypes.Structure):
    _fields_ = [
        ("dwDebugEventCode", wintypes.DWORD),
        ("dwProcessId", wintypes.DWORD),
        ("dwThreadId", wintypes.DWORD),
        ("u", DEBUG_EVENT_UNION),
    ]


class MEMORY_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("BaseAddress", ctypes.c_void_p),
        ("AllocationBase", ctypes.c_void_p),
        ("AllocationProtect", wintypes.DWORD),
        ("RegionSize", ctypes.c_size_t),
        ("State", wintypes.DWORD),
        ("Protect", wintypes.DWORD),
        ("Type", wintypes.DWORD),
    ]


class WOW64_CONTEXT(ctypes.Structure):
    _fields_ = [
        ("ContextFlags", wintypes.DWORD),
        ("Dr0", wintypes.DWORD),
        ("Dr1", wintypes.DWORD),
        ("Dr2", wintypes.DWORD),
        ("Dr3", wintypes.DWORD),
        ("Dr6", wintypes.DWORD),
        ("Dr7", wintypes.DWORD),
        ("FloatSave", ctypes.c_byte * 112),
        ("SegGs", wintypes.DWORD),
        ("SegFs", wintypes.DWORD),
        ("SegEs", wintypes.DWORD),
        ("SegDs", wintypes.DWORD),
        ("Edi", wintypes.DWORD),
        ("Esi", wintypes.DWORD),
        ("Ebx", wintypes.DWORD),
        ("Edx", wintypes.DWORD),
        ("Ecx", wintypes.DWORD),
        ("Eax", wintypes.DWORD),
        ("Ebp", wintypes.DWORD),
        ("Eip", wintypes.DWORD),
        ("SegCs", wintypes.DWORD),
        ("EFlags", wintypes.DWORD),
        ("Esp", wintypes.DWORD),
        ("SegSs", wintypes.DWORD),
        ("ExtendedRegisters", ctypes.c_byte * 512),
    ]


kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.CreateProcessW.argtypes = [
    wintypes.LPCWSTR,
    wintypes.LPWSTR,
    ctypes.c_void_p,
    ctypes.c_void_p,
    wintypes.BOOL,
    wintypes.DWORD,
    ctypes.c_void_p,
    wintypes.LPCWSTR,
    ctypes.POINTER(STARTUPINFO),
    ctypes.POINTER(PROCESS_INFORMATION),
]
kernel32.CreateProcessW.restype = wintypes.BOOL
kernel32.WaitForDebugEvent.argtypes = [ctypes.POINTER(DEBUG_EVENT), wintypes.DWORD]
kernel32.WaitForDebugEvent.restype = wintypes.BOOL
kernel32.ContinueDebugEvent.argtypes = [wintypes.DWORD, wintypes.DWORD, wintypes.DWORD]
kernel32.ReadProcessMemory.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_size_t,
    ctypes.POINTER(ctypes.c_size_t),
]
kernel32.WriteProcessMemory.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_size_t,
    ctypes.POINTER(ctypes.c_size_t),
]
kernel32.VirtualQueryEx.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.POINTER(MEMORY_BASIC_INFORMATION),
    ctypes.c_size_t,
]
kernel32.VirtualQueryEx.restype = ctypes.c_size_t
kernel32.FlushInstructionCache.argtypes = [
    wintypes.HANDLE,
    ctypes.c_void_p,
    ctypes.c_size_t,
]
kernel32.OpenThread.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
kernel32.OpenThread.restype = wintypes.HANDLE
kernel32.Wow64GetThreadContext.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(WOW64_CONTEXT),
]
kernel32.Wow64SetThreadContext.argtypes = [
    wintypes.HANDLE,
    ctypes.POINTER(WOW64_CONTEXT),
]
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]


def fail(msg):
    raise ctypes.WinError(ctypes.get_last_error(), msg)


def read_mem(hp, addr, size):
    buf = ctypes.create_string_buffer(size)
    got = ctypes.c_size_t()
    if not kernel32.ReadProcessMemory(
        hp, ctypes.c_void_p(addr), buf, size, ctypes.byref(got)
    ):
        return None
    return buf.raw[: got.value]


def write_mem(hp, addr, data):
    buf = ctypes.create_string_buffer(data)
    done = ctypes.c_size_t()
    if not kernel32.WriteProcessMemory(
        hp, ctypes.c_void_p(addr), buf, len(data), ctypes.byref(done)
    ):
        fail("WriteProcessMemory")
    kernel32.FlushInstructionCache(hp, ctypes.c_void_p(addr), len(data))


def get_ctx(tid):
    thread = kernel32.OpenThread(0x001F03FF, False, tid)
    if not thread:
        fail("OpenThread")
    try:
        ctx = WOW64_CONTEXT()
        ctx.ContextFlags = CONTEXT_FULL
        if not kernel32.Wow64GetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64GetThreadContext")
        return ctx
    finally:
        kernel32.CloseHandle(thread)


def set_eip(tid, eip):
    thread = kernel32.OpenThread(0x001F03FF, False, tid)
    if not thread:
        fail("OpenThread")
    try:
        ctx = WOW64_CONTEXT()
        ctx.ContextFlags = CONTEXT_FULL
        if not kernel32.Wow64GetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64GetThreadContext")
        ctx.Eip = eip
        if not kernel32.Wow64SetThreadContext(thread, ctypes.byref(ctx)):
            fail("Wow64SetThreadContext")
    finally:
        kernel32.CloseHandle(thread)


def install_bps(hp, base):
    bps = {}
    for rva, name in RVA_BPS.items():
        addr = base + rva
        orig = read_mem(hp, addr, 1)
        if not orig:
            raise RuntimeError(f"cannot read breakpoint byte at {addr:08X}")
        write_mem(hp, addr, b"\xcc")
        bps[addr] = (orig, name, rva)
        print(f"bp {addr:08X} ({name})")
    return bps


def search_and_dump_xml(hp):
    mbi = MEMORY_BASIC_INFORMATION()
    addr = 0
    hits = []
    while addr < 0x80000000:
        ret = kernel32.VirtualQueryEx(
            hp, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)
        )
        if not ret:
            addr += 0x10000
            continue
        base = int(mbi.BaseAddress or 0)
        size = int(mbi.RegionSize)
        readable = mbi.State == MEM_COMMIT and not (
            mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)
        )
        if readable and size:
            data = read_mem(hp, base, min(size, 64 * 1024 * 1024))
            if data:
                for needle in (b"<?xml", b"<chiplist", b"SPI_NOR", b"SPI_NAND"):
                    pos = data.find(needle)
                    if pos != -1:
                        hits.append(
                            (base + pos, needle.decode("ascii", "ignore"), base, data)
                        )
        addr = base + max(size, 0x1000)

    for hit_addr, needle, base, data in hits:
        print(f"hit {needle} at {hit_addr:08X}")

    candidates = []
    for hit_addr, needle, base, data in hits:
        for marker in (b"<?xml", b"<chiplist"):
            start = data.find(marker)
            end = data.find(b"</chiplist>", start)
            if start != -1 and end != -1:
                end += len(b"</chiplist>")
                candidates.append(
                    (0 if marker == b"<?xml" else 1, base + start, data[start:end])
                )

    if candidates:
        candidates.sort(key=lambda item: (item[0], item[1]))
        _, start_addr, xml = candidates[0]
        with open(OUT_XML, "wb") as f:
            f.write(xml)
        print(f"dumped {len(xml)} bytes from {start_addr:08X} to {OUT_XML}")
        return True
    return False


def main():
    si = STARTUPINFO()
    si.cb = ctypes.sizeof(si)
    pi = PROCESS_INFORMATION()
    cmd = f'"{APP_EXE}"'
    if not kernel32.CreateProcessW(
        None,
        cmd,
        None,
        None,
        False,
        DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,
        None,
        APP_DIR,
        ctypes.byref(si),
        ctypes.byref(pi),
    ):
        fail("CreateProcessW")
    print(f"started pid={pi.dwProcessId}")

    hp = pi.hProcess
    bps = {}
    base = None
    saw_initial_bp = False
    dumped = False
    try:
        while True:
            ev = DEBUG_EVENT()
            if not kernel32.WaitForDebugEvent(ctypes.byref(ev), 30000):
                print("timeout waiting for debug event")
                break
            status = DBG_CONTINUE
            if ev.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT:
                base = int(ev.u.CreateProcessInfo.lpBaseOfImage)
                print(f"image base {base:08X}")
                bps = install_bps(hp, base)
                if ev.u.CreateProcessInfo.hFile:
                    kernel32.CloseHandle(ev.u.CreateProcessInfo.hFile)
            elif ev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
                ex = ev.u.Exception.ExceptionRecord
                code = ex.ExceptionCode
                exaddr = int(ex.ExceptionAddress or 0)
                if code in (EXCEPTION_BREAKPOINT, STATUS_WX86_BREAKPOINT):
                    bp_addr = exaddr - 1
                    if bp_addr not in bps and exaddr in bps:
                        bp_addr = exaddr
                    if not saw_initial_bp and bp_addr not in bps:
                        saw_initial_bp = True
                        print(f"initial/system breakpoint at {exaddr:08X}")
                    elif bp_addr in bps:
                        orig, name, rva = bps.pop(bp_addr)
                        write_mem(hp, bp_addr, orig)
                        set_eip(ev.dwThreadId, bp_addr)
                        ctx = get_ctx(ev.dwThreadId)
                        stack0 = read_mem(hp, ctx.Esp, 4)
                        stack0_val = struct.unpack("<I", stack0)[0] if stack0 else None
                        stack0_text = (
                            f"{stack0_val:08X}"
                            if stack0_val is not None
                            else "????????"
                        )
                        print(
                            f"break {bp_addr:08X} {name}: "
                            f"EAX={ctx.Eax:08X} ECX={ctx.Ecx:08X} EDX={ctx.Edx:08X} "
                            f"ESP={ctx.Esp:08X} [ESP]={stack0_text}"
                        )
                        if rva == 0x5B87F:
                            dumped = search_and_dump_xml(hp)
                            if dumped:
                                break
                        elif rva == 0x27D1C:
                            dumped = search_and_dump_xml(hp)
                            break
                    else:
                        status = DBG_EXCEPTION_NOT_HANDLED
                elif code == EXCEPTION_SINGLE_STEP:
                    pass
                else:
                    print(f"exception {code:08X} at {exaddr:08X}")
                    status = DBG_EXCEPTION_NOT_HANDLED
            elif ev.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT:
                print(f"process exited {ev.u.ExitProcess.dwExitCode}")
                break
            kernel32.ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, status)
    finally:
        if not dumped:
            print("XML not dumped")


if __name__ == "__main__":
    main()

Update cơ chế decrypt từ chiplist.dat:

import hashlib
import base64
import pathlib
import struct
import zlib
INPUT = pathlib.Path("chiplist.dat")
OUTPUT = pathlib.Path("chiplist_algorithm.xml")
def rc4_init(key: bytes) -> list[int]:
    s = list(range(256))
    j = 0
    key_len = len(key)
    for i in range(256):
        j = (j + s[i] + key[i % key_len]) & 0xFF
        s[i], s[j] = s[j], s[i]
    return s
def rc4_apply(data: bytes, s: list[int]) -> bytes:
    i = 0
    j = 0
    out = bytearray(len(data))
    for n, b in enumerate(data):
        i = (i + 1) & 0xFF
        j = (j + s[i]) & 0xFF
        s[i], s[j] = s[j], s[i]
        out[n] = b ^ s[(s[i] + s[j]) & 0xFF]
    return bytes(out)
def rc4(data: bytes, key: bytes) -> bytes:
    return rc4_apply(data, rc4_init(key))
def rc4_chunked(data: bytes, key: bytes, chunk_size: int = 0x2000) -> bytes:
    s = rc4_init(key)
    out = bytearray()
    for offset in range(0, len(data), chunk_size):
        out += rc4_apply(data[offset:offset + chunk_size], s)
    return bytes(out)
def build_password() -> str:
    first_order = ["vd7", "SQP", "RBs", "HgX", "0bv", "pii", "sn8"]
    second_order = ["z1J", "92Z", "7xA", "eex", "MEW", "ulI", "wdX"]
    return "".join(s[1:3] for s in first_order + second_order)
def derive_real_password() -> bytes:
    obfuscated = base64.b64decode(build_password().encode("ascii"))
    unwrap_key = hashlib.sha1(b"chiplist.dat").digest()
    return rc4(obfuscated, unwrap_key)
def decrypt_chiplist(raw: bytes) -> bytes:
    if len(raw) < 8:
        raise ValueError(f"chiplist data is too short: {len(raw)} bytes")

    password = derive_real_password()
    key = hashlib.sha1(password).digest()
    decrypted = rc4_chunked(raw, key)

    unpacked_size, reserved = struct.unpack_from("<II", decrypted, 0)
    if reserved != 0:
        raise ValueError(f"unexpected header reserved value: 0x{reserved:08X}")

    xml = zlib.decompress(decrypted[8:])
    if len(xml) != unpacked_size:
        raise ValueError(f"size mismatch: header={unpacked_size}, zlib={len(xml)}")
    return xml
def main():
    xml = decrypt_chiplist(INPUT.read_bytes())
    OUTPUT.write_bytes(xml)
    print(f"obfuscated password: {build_password()}")
    print(f"real password: {derive_real_password().decode('ascii')}")
    print(f"wrote {len(xml)} bytes to {OUTPUT}")
if __name__ == "__main__":
    main()

Dựa vào cơ chế này xây dựng 1 ứng dụng GUI cho phép chỉnh sửa trực tiếp file chiplist.dat

Source GUI:

import hashlib
import base64
import struct
import zlib
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import xml.etree.ElementTree as ET
import os
import re
from PIL import Image, ImageTk

# ============== Phần mã hóa/giải mã (Đã sửa lỗi Chunked RC4) ==============
def rc4_neo_crypt(data: bytes, key: bytes, chunk_size: int = None) -> bytes:
    """Thuật toán RC4 đặc biệt của NeoProgrammer: i, j reset mỗi chunk"""
    # Khởi tạo S-box
    s = list(range(256))
    j = 0
    key_len = len(key)
    for i in range(256):
        j = (j + s[i] + key[i % key_len]) & 0xFF
        s[i], s[j] = s[j], s[i]
    
    # Xử lý dữ liệu
    out = bytearray()
    # Nếu không có chunk_size (dùng cho password), coi như 1 chunk duy nhất
    c_size = chunk_size if chunk_size else len(data)
    
    for offset in range(0, len(data), c_size):
        chunk = data[offset : offset + c_size]
        curr_i = curr_j = 0 # ĐIỂM QUAN TRỌNG: i, j reset về 0 mỗi chunk
        for b in chunk:
            curr_i = (curr_i + 1) & 0xFF
            curr_j = (curr_j + s[curr_i]) & 0xFF
            s[curr_i], s[curr_j] = s[curr_j], s[curr_i]
            out.append(b ^ s[(s[curr_i] + s[curr_j]) & 0xFF])
    return bytes(out)

def build_password() -> str:
    parts = ["vd7", "SQP", "RBs", "HgX", "0bv", "pii", "sn8", "z1J", "92Z", "7xA", "eex", "MEW", "ulI", "wdX"]
    return "".join(s[1:3] for s in parts)

def derive_real_password() -> bytes:
    obf = base64.b64decode(build_password().encode("ascii"))
    unwrap_key = hashlib.sha1(b"chiplist.dat").digest()
    # Password không dùng chunking (hoặc chunk_size = len(data))
    return rc4_neo_crypt(obf, unwrap_key)

def encrypt_chiplist(xml_data: bytes) -> bytes:
    key = hashlib.sha1(derive_real_password()).digest()
    # Header: Size (4 bytes) + Reserved (4 bytes)
    plain = struct.pack("<II", len(xml_data), 0) + zlib.compress(xml_data)
    return rc4_neo_crypt(plain, key, chunk_size=0x2000)

def decrypt_chiplist(raw: bytes) -> bytes:
    if len(raw) < 8: raise ValueError("Data too short")
    key = hashlib.sha1(derive_real_password()).digest()
    dec = rc4_neo_crypt(raw, key, chunk_size=0x2000)
    
    unpacked_size, reserved = struct.unpack_from("<II", dec, 0)
    # Giải nén từ byte thứ 8 trở đi
    return zlib.decompress(dec[8:])

# ============== Phần GUI (Giữ nguyên cấu trúc tối ưu) ==============
class ChipEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("ChipList Editor - NeoProgrammer Database")
        self.center_window(self.root, 780, 750)
        self.current_file, self.xml_root, self.search_var = None, None, tk.StringVar()
        self.setup_menu()
        self.setup_main_layout()
        self.new_file()
    
    def center_window(self, win, w, h):
        """Hàm bổ trợ để đưa cửa sổ ra giữa màn hình"""
        ws = win.winfo_screenwidth()
        hs = win.winfo_screenheight()
        x = (ws // 2) - (w // 2)
        y = (hs // 2) - (h // 2)
        win.geometry(f"{w}x{h}+{x}+{y}")
    
    def setup_menu(self):
        m = tk.Menu(self.root)
        self.root.config(menu=m)
        f = tk.Menu(m, tearoff=0)
        m.add_cascade(label="File", menu=f)
        f.add_command(label="New", command=self.new_file, accelerator="Ctrl+N")
        f.add_command(label="Open XML...", command=self.open_xml, accelerator="Ctrl+O")
        f.add_command(label="Open DAT...", command=self.open_dat, accelerator="Ctrl+Shift+O")
        f.add_separator()
        f.add_command(label="Save as XML...", command=self.save_as_xml, accelerator="Ctrl+S")
        f.add_command(label="Save as DAT...", command=self.save_as_dat, accelerator="Ctrl+Shift+S")
        f.add_separator()
        f.add_command(label="Exit", command=self.root.quit)

        e = tk.Menu(m, tearoff=0)
        m.add_cascade(label="Edit", menu=e)
        e.add_command(label="Add Chip...", command=self.add_chip, accelerator="Insert")
        e.add_command(label="Delete Chip", command=self.delete_chip, accelerator="Delete")
        
        self.root.bind("<Control-n>", lambda event: self.new_file())
        self.root.bind("<Control-o>", lambda event: self.open_xml())
        self.root.bind("<Control-s>", lambda event: self.save_as_xml())
        self.root.bind("<Control-S>", lambda event: self.save_as_dat())
        self.root.bind("<Insert>", lambda event: self.add_chip())
        self.root.bind("<Delete>", lambda event: self.delete_chip())

    def setup_main_layout(self):
        p = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
        p.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        left = ttk.Frame(p)
        p.add(left, weight=1)
        sf = ttk.Frame(left)
        sf.pack(fill=tk.X, pady=5)
        self.search_var.trace_add('write', lambda *a: self.update_tree())
        ttk.Entry(sf, textvariable=self.search_var).pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        self.tree = ttk.Treeview(left, show="tree", columns=("path",))
        self.tree.pack(fill=tk.BOTH, expand=True)
        self.tree.bind("<<TreeviewSelect>>", self.on_tree_select)
        self.tree.bind("<Double-1>", lambda e: self.edit_chip())
        
        right = ttk.Frame(p)
        p.add(right, weight=2)

        # Chia khung bên phải làm 2 phần: trên là chữ, dưới là ảnh
        self.details_text = tk.Text(right, wrap=tk.WORD, font=("Consolas", 10), height=12)
        self.details_text.pack(fill=tk.X, side=tk.TOP)

        # Khung chứa ảnh
        self.img_label = ttk.Label(right, text="No Adapter Image", anchor=tk.CENTER)
        self.img_label.pack(fill=tk.BOTH, expand=True, pady=5)

        self.status_var = tk.StringVar(value="Ready")
        ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(side=tk.BOTTOM, fill=tk.X)

    def is_element_node(self, elem):
        return isinstance(elem, ET.Element) and elem.tag != 'Comment'

    def new_file(self):
        self.xml_root = ET.Element("chiplist")
        for cat in ["SPI_NOR", "SPI_NAND", "I2C", "AVR"]: ET.SubElement(self.xml_root, cat)
        self.current_file = None
        self.update_tree()

    def update_tree(self):
        self.tree.delete(*self.tree.get_children())
        kw = self.search_var.get().lower()
        if self.xml_root is None: return

        for cat in self.xml_root:
            if not self.is_element_node(cat): continue
            cat_node = None
            for manu in cat:
                if not self.is_element_node(manu): continue
                manu_node = None
                for chip in manu:
                    if not self.is_element_node(chip): continue
                    # Hiển thị: bỏ dấu gạch dưới nếu nó là ký tự đầu tiên
                    display_name = chip.tag[1:] if chip.tag.startswith("_") else chip.tag
                    if kw and kw not in display_name.lower() and kw not in manu.tag.lower(): continue
                    
                    if cat_node is None: cat_node = self.tree.insert("", "end", text=f"[{cat.tag}]", open=True)
                    if manu_node is None: manu_node = self.tree.insert(cat_node, "end", text=manu.tag, open=True)
                    
                    size = chip.get("size", "")
                    if size.isdigit():
                        sz = int(size)
                        size = f" ({sz/1024:.0f}KB)" if sz < 1048576 else f" ({sz/1048576:.0f}MB)"
                    
                    path = f"{cat.tag}|{manu.tag}|{chip.tag}"
                    self.tree.insert(manu_node, "end", text=f"{display_name}{size}", values=(path,), tags=("chip",))
        self.update_status()
        
    def update_status(self):
        count = sum(len([c for c in manu if self.is_element_node(c)]) 
                   for cat in self.xml_root if self.is_element_node(cat) 
                   for manu in cat if self.is_element_node(manu))
        self.status_var.set(f"Total Chips: {count} | {os.path.basename(self.current_file or 'New File')}")

    def _get_chip_elements(self, path):
        c_tag, m_tag, ch_tag = path.split('|')
        cat_el = next(c for c in self.xml_root if c.tag == c_tag)
        manu_el = next(m for m in cat_el if m.tag == m_tag)
        chip_el = next(ch for ch in manu_el if ch.tag == ch_tag)
        return cat_el, manu_el, chip_el

    def on_tree_select(self, event):
        item = self.tree.selection()
        if not item or "chip" not in self.tree.item(item[0], "tags"): return
        path = self.tree.item(item[0], "values")[0]

        # Lấy thông tin category, manufacturer và chip element
        cat_tag, manu, _ = path.split('|')
        _, _, chip_el = self._get_chip_elements(path)

        # 1. Hiển thị thông tin text
        self.details_text.delete(1.0, tk.END)
        res = f"Category: {cat_tag}\nManufacturer: {manu}\nChip: {chip_el.tag}\n" + "-"*30 + "\n"
        res += "\n".join(f"{k:10}: {v}" for k, v in chip_el.attrib.items())
        self.details_text.insert(tk.END, res)

        # 2. Xử lý hiển thị ảnh theo logic yêu cầu
        self.process_adapter_logic(cat_tag, chip_el)

    def process_adapter_logic(self, category, chip_el):
        vcc = chip_el.get("vcc", "")
        adapt = chip_el.get("adapt", "")
        img_name = None

        # --- QUY TẮC ƯU TIÊN ---

        # 3. Đối với category là AVR -> luôn hiện scheme_AVRISP.jpg
        if category == "AVR":
            img_name = "scheme_AVRISP.jpg"

        # 1. SPI_xx và VCC 1.8
        elif category.startswith("SPI") and vcc == "1.8":
            img_name = "SPI_1.8v_Adapter.jpg"

        # 2. I2C_xx và VCC 1.8
        elif category.startswith("I2C") and vcc == "1.8":
            img_name = "I2C_1.8v_Adapter.jpg"

        # --- QUY TẮC THEO BẢNG MAPPING (Dựa trên tham số adapt) ---
        else:
            mapping = {
                "adapTC8912": "scheme_TC8912x.jpg", # Ưu tiên theo tên cụ thể
                "adap59C": "scheme_ER59xx.jpg",
                "adapN76E": "scheme_N76Exx.jpg",
                "adapKB90": "scheme_KB901x.jpg",
                "adapM35080": "scheme_M35080.jpg",
                "adapI2CM34": "scheme_I2C_M34E0x.jpg",
                "adapI2C": "scheme_I2C.jpg",
                "adapCT1C": "scheme_CT1C08.jpg",
                "adapBR90": "scheme_BR90xx.jpg",
                "adap93S": "scheme_93Sxx.jpg",
                "adap93C": "scheme_93Cxx.jpg",
                "adap93Cx5": "scheme_93Cx5.jpg"
            }

            if adapt in mapping:
                img_name = mapping[adapt]
            elif adapt.startswith("adap"):
                # Mặc định nếu không nằm trong mapping nhưng có tiền tố adap
                # Ví dụ: adapSPI45 -> scheme_SPI45.jpg
                suffix = adapt.replace("adap", "")
                img_name = f"scheme_{suffix}.jpg"

        # Gọi hàm hiển thị ảnh lên giao diện
        self.show_image(img_name)

    def show_image(self, img_file):
        """Hàm thực thi việc load ảnh và vẽ lên Label"""
        if not img_file:
            self.img_label.config(image='', text="No specific adapter required")
            return

        # Đường dẫn: .../Adapters/CH341/
        script_dir = os.path.dirname(os.path.abspath(__file__))
        path = os.path.join(script_dir, "Adapters", "CH341", img_file)

        if os.path.exists(path):
            try:
                img = Image.open(path)
                # Resize ảnh cho phù hợp với khung (ví dụ rộng tối đa 450px)
                w_max = 450
                ratio = w_max / float(img.size[0])
                h_size = int(float(img.size[1]) * float(ratio))
                img = img.resize((w_max, h_size), Image.Resampling.LANCZOS)

                photo = ImageTk.PhotoImage(img)
                self.img_label.config(image=photo, text="")
                self.img_label.image = photo # Quan trọng: giữ reference
            except Exception as e:
                self.img_label.config(image='', text=f"Error loading: {img_file}")
        else:
            self.img_label.config(image='', text=f"Missing image: {img_file}")

    def _show_chip_form(self, cat_tag, manu_tag="", chip_el=None):
        dialog = tk.Toplevel(self.root)
        is_edit = chip_el is not None  # Kiểm tra rõ ràng

        if is_edit:
            dialog.title("Edit Chip Info")
        else:
            dialog.title("Add New Chip")

        self.center_window(dialog, 350, 550)

        # Đợi dialog được tạo xong mới grab
        dialog.update_idletasks()
        dialog.grab_set()

        # Danh sách field cần hiển thị
        fields = ["Name", "Manufacturer", "page", "size", "id", "vcc", "adapt", "comment"]
        if cat_tag == "SPI_NAND":
            fields += ["besize", "spare", "dies", "planes"]
        elif cat_tag == "SPI_NOR":
            fields += ["secreg0", "secreg1", "secreg2", "secreg3", "vmod"]
        elif cat_tag == "AVR":
            fields += ["pageep", "sizeeep"]

        entries = {}

        # Tạo các widget nhập liệu
        for i, f in enumerate(fields):
            ttk.Label(dialog, text=f"{f}:").grid(row=i, column=0, padx=10, pady=5, sticky="w")
            en = ttk.Entry(dialog, width=35)
            en.grid(row=i, column=1, padx=10, pady=5)
            entries[f.lower()] = en

            # Điền dữ liệu nếu đang edit
            if is_edit:
                if f == "Name":
                    # Bỏ dấu _ ở đầu nếu có (khi hiển thị)
                    display_name = chip_el.tag
                    if display_name.startswith("_"):
                        display_name = display_name[1:]
                    en.insert(0, display_name)
                elif f == "Manufacturer":
                    en.insert(0, manu_tag)
                else:
                    # Lấy giá trị từ attrib, nếu không có thì để trống
                    val = chip_el.get(f.lower(), "")
                    en.insert(0, val)
            elif f == "Manufacturer":
                # Nếu thêm mới và có manufacturer mặc định
                if manu_tag:
                    en.insert(0, manu_tag)

        def save():
            nonlocal chip_el # Lấy tên chip và xử lý name = entries['name'].get().strip() if not name: messagebox.showwarning("Warning", "Chip name cannot be empty!") return name = name.replace(" ", "_") # Nếu tên bắt đầu bằng số, tự động thêm dấu "_" ở đầu if name and name[0].isdigit(): name = "_" + name # Lấy manufacturer manu = entries['manufacturer'].get().strip() if not manu: messagebox.showwarning("Warning", "Manufacturer cannot be empty!") return manu = manu.replace(" ", "_") # Tìm category element cat_el = None for c in self.xml_root: if c.tag == cat_tag: cat_el = c break if cat_el is None: messagebox.showerror("Error", f"Category '{cat_tag}' not found!") return # Xử lý lưu chip (thêm mới hoặc cập nhật) if is_edit: # Edit mode: cập nhật chip hiện có # Nếu manufacturer thay đổi, cần di chuyển chip if manu != manu_tag: # Tìm manufacturer cũ và xóa chip khỏi đó old_manu_el = None for m in cat_el: if m.tag == manu_tag: old_manu_el = m break if old_manu_el: old_manu_el.remove(chip_el) # Xóa manufacturer nếu không còn chip nào if len(old_manu_el) == 0: cat_el.remove(old_manu_el) # Tìm hoặc tạo manufacturer mới new_manu_el = None for m in cat_el: if m.tag == manu: new_manu_el = m break if new_manu_el is None: new_manu_el = ET.SubElement(cat_el, manu) new_manu_el.append(chip_el) # Cập nhật tên chip chip_el.tag = name else: # Add mode: tạo chip mới # Tìm hoặc tạo manufacturer manu_el = None for m in cat_el: if m.tag == manu: manu_el = m break if manu_el is None: manu_el = ET.SubElement(cat_el, manu) # Tạo chip mới chip_el = ET.SubElement(manu_el, name) # Cập nhật các attributes skip_fields = ["name", "manufacturer"] for f in fields: field_name = f.lower() if field_name in skip_fields: continue val = entries[field_name].get().strip() if val: chip_el.set(field_name, val) else: # Xóa attribute nếu tồn tại và giá trị rỗng if field_name in chip_el.attrib: del chip_el.attrib[field_name] self.update_tree() dialog.destroy() # Nút Save và Cancel btn_frame = ttk.Frame(dialog) btn_frame.grid(row=len(fields), column=0, columnspan=2, pady=20) ttk.Button(btn_frame, text="Save", command=save, width=15).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="Cancel", command=dialog.destroy, width=15).pack(side=tk.LEFT, padx=5) # Đảm bảo dialog ở trên cùng dialog.transient(self.root) dialog.focus_force() def add_chip(self): # Lấy danh sách tên các Category hiện có trong XML cats = [c.tag for c in self.xml_root if self.is_element_node(c)] if not cats: messagebox.showerror("Error", "No categories found in the database!") return # Tạo cửa sổ phụ để chọn Category dialog = tk.Toplevel(self.root) dialog.title("Select Category") self.center_window(dialog, 300, 150) dialog.grab_set() # Khóa cửa sổ chính cho đến khi chọn xong dialog.resizable(False, False) ttk.Label(dialog, text="Choose a category:").pack(pady=10) # Menu thả xuống chứa danh sách category cat_var = tk.StringVar() combo = ttk.Combobox(dialog, textvariable=cat_var, values=cats, state="readonly") combo.pack(padx=20, fill=tk.X) combo.current(0) # Mặc định chọn cái đầu tiên def on_next(): selected_cat = cat_var.get() dialog.destroy() self._show_chip_form(selected_cat) # Gọi form nhập liệu với category đã chọn ttk.Button(dialog, text="Next", command=on_next).pack(pady=10) def edit_chip(self): item = self.tree.selection() if item and "chip" in self.tree.item(item[0], "tags"): path = self.tree.item(item[0], "values")[0] parts = path.split('|') if len(parts) == 3: cat_tag = parts[0] manu_tag = parts[1] chip_tag = parts[2] _, _, chip_el = self._get_chip_elements(path) self._show_chip_form(cat_tag, manu_tag, chip_el) def delete_chip(self): item = self.tree.selection() if item and "chip" in self.tree.item(item[0], "tags"): path = self.tree.item(item[0], "values")[0] if messagebox.askyesno("Confirm", f"Delete {path.split('|')[-1]}?"): cat_el, manu_el, chip_el = self._get_chip_elements(path) manu_el.remove(chip_el) if len(manu_el) == 0: cat_el.remove(manu_el) self.update_tree() def open_xml(self): path = filedialog.askopenfilename(filetypes=[("XML Files", "*.xml")]) if path: with open(path, "r", encoding="utf-8") as f: # Tối ưu: Tự động thêm dấu _ vào trước các thẻ bắt đầu bằng số để tránh lỗi parse content = re.sub(r'<(/?)([0-9])', r'<\1_\2', f.read()) self.xml_root = ET.fromstring(content) self.current_file = path self.update_tree() def open_dat(self): path = filedialog.askopenfilename(filetypes=[("DAT Files", "*.dat")]) if path: try: with open(path, "rb") as f: raw_data = decrypt_chiplist(f.read()).decode("utf-8") # Tối ưu: Xử lý thẻ số tương tự như open_xml content = re.sub(r'<(/?)([0-9])', r'<\1_\2', raw_data) self.xml_root = ET.fromstring(content) self.current_file = path self.update_tree() except Exception as e: messagebox.showerror("Decryption Error", f"Failed to decrypt: {e}") def get_formatted_xml(self): """Tạo XML có Header/Comment chuẩn và tự động sửa thẻ bắt đầu bằng số""" header = '<?xml version="1.0" encoding="utf-8"?>' comment = """<!--- size - memory chip data size in bytes (Decimal) page - memory chip page size in bytes and in WORD for ATmega AVR (Decimal). For SST AAI Word programm - SSTW. For SST AAI Byte programm - SSTB. pageep - ATmega Eeprom page size in bytes (Decimal) sizeeep - ATmega Eeprom memory size in bytes (Decimal) id - Memory chip identifier (HEX). vcc - voltage (1.8, 3.3, 5.0) for information only, (3.3 default if absent) dies - number of dies (for large spi flash over 256Mo) planes - number of planes (for some SPI NAND) script - script file name from scripts folder besize - block erase size in kbytes with opcode $D8 (SPI NOR/NAND) adapt - adapter (adapKB90, adapI2C, adapI2CM34, adapSPI45, adap93C, adap93S, adap59C, adapM35080, adapCT1C) comment- Any comment otpsize- OTP size (EON) secreg0, secreg1, secreg2, secreg3 - Security registers size (winbond/gigadevice) Vmod - Vmod3.3v, Vmod5.0v -->""" lines = [header, comment, "<chiplist>"] for cat in self.xml_root: if not self.is_element_node(cat): continue lines.append(f" <{cat.tag}>") for manu in cat: if not self.is_element_node(manu): continue lines.append(f" <{manu.tag}>") for chip in manu: if not self.is_element_node(chip): continue # Đảm bảo tên chip không bắt đầu bằng số khi lưu tag = "_" + chip.tag if chip.tag[0].isdigit() else chip.tag attrs = " ".join([f'{k}="{v}"' for k, v in chip.attrib.items()]) lines.append(f" <{tag} {attrs}/>") lines.append(f" </{manu.tag}>") lines.append(f" </{cat.tag}>") lines.append("</chiplist>") return "\n".join(lines).encode("utf-8") def save_as_xml(self): path = filedialog.asksaveasfilename(defaultextension=".xml", filetypes=[("XML Files", "*.xml")]) if path: try: formatted_data = self.get_formatted_xml() with open(path, "wb") as f: f.write(formatted_data) messagebox.showinfo("Success", "XML saved with standard Header.") except Exception as e: messagebox.showerror("Error", f"Failed to save XML: {e}") def save_as_dat(self): path = filedialog.asksaveasfilename(defaultextension=".dat", filetypes=[("DAT Files", "*.dat")]) if path: try: xml_data = self.get_formatted_xml() # Lấy dữ liệu đã format chuẩn with open(path, "wb") as f: f.write(encrypt_chiplist(xml_data)) messagebox.showinfo("Success", "DAT saved successfully.") except Exception as e: messagebox.showerror("Error", f"Failed to save DAT: {e}") if __name__ == "__main__": root = tk.Tk() app = ChipEditor(root) root.mainloop()




Không có nhận xét nào:

Đăng nhận xét

Cám ơn bạn đã để lại nhận xét

Bài đăng nổi bật

Giới thiệu bộ lập trình giá rẻ CH341A và phần mềm NeoProgrammer 2.2.0.10 “quốc dân” cho dân điện tử

Trong lĩnh vực sửa chữa phần cứng, lập trình firmware hay nghiên cứu hệ thống nhúng, cái tên CH341A gần như đã trở thành “huyền thoại” tron...

Popular Posts