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:
- Người dùng kết nối CH341A với máy tính qua USB
- Gắn chip cần đọc/ghi vào socket hoặc dùng kẹp SOP8 (SOIC clip)
- 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:
- Mở rộng danh sách chip hỗ trợ (hiện tại T4/2026 là 2110 loại IC - Luôn được cập nhật)
- Cải thiện tốc độ đọc/ghi
- Sửa lỗi nhận diện chip
- Tối ưu giao tiếp với CH341A
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:
- Kết nối CH341A với máy tính
- Gắn chip vào socket hoặc kẹp SOP8
- Mở NeoProgrammer
- Nhấn Detect IC để nhận diện chip hoặc chọn thủ công
- 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ểm | Nhượ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()
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():
# 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()







