Bỏ qua để đến nội dung

delta.dat

Tài nguyên game nằm gọn trong hai archive: AvatarStar.pde nặng 1,27 GB chứa model với map, và một language pack cho mỗi thứ tiếng, chẳng hạn AvatarStar_vi_vn.pde khoảng 33 MB. Không có cơ chế nào khác thì sửa một dòng text tiếng Việt cũng bắt người chơi tải lại đủ 33 MB, còn sửa một cái mesh là 1,27 GB.

delta.dat sinh ra để khỏi phải làm vậy. Định dạng này của chính nhà phát hành chứ không phải thứ ta tự chế ra, kênh Đài Loan ship kiểu này nhiều năm nay rồi.

Nó là payload của chế độ PatchMode="Patch". Chưa rõ chế độ đó khác Replace chỗ nào thì đọc Replace và Patch trước, trang này đi vào bên trong chính file delta.dat.

Trong client không có bsdiff, xdelta hay vcdiff. Không tính offset, không có window, không có checksum nào đem ra đối chiếu. Đơn vị nhỏ nhất gửi được là một file nằm trong archive, không phải một dải byte. Sửa một byte trong mesh 8 KB thì vẫn gửi đủ 8 KB.

Thô như vậy nhưng vẫn ăn đứt cách cũ. Số đo ngày 2026-08-29:

Cách gửi Trên đường truyền
PatchMode="Replace" cho AvatarStar.pde 1.270.980.608 B
PatchMode="Patch", thêm một file Lua 129 byte 45.056 B

Nặng nhẹ là do phần thay đổi, không phải do archive to hay nhỏ. Vì vậy nên xếp tài nguyên đúng chỗ: thứ riêng cho từng thứ tiếng để trong language pack, thứ dùng chung để trong base archive. Trước đây mọi người cố nhồi nội dung vào pack 51 MB cho khỏi phải đụng cú 1,27 GB, mà đúng ra cú đó chưa bao giờ cần phải có.

Không có container riêng nào cả. delta.dat là archive .pde như hai archive nó sắp vá. Mở ra thì thấy:

/patch.lst danh sách thao tác, text thuần
/AvatarStar.pde/ payload cho archive này
/AvatarStar_vi_vn.pde/ payload cho archive kia
/client.exe file ghi thẳng ra ổ đĩa

Thư mục con nào trùng tên với một archive thì mọi thứ bên trong thuộc về archive đó. Còn lại ghi thẳng vào thư mục client.

patch.lst là phần điều khiển, nó nói cho client biết phải làm gì với từng file:

P AvatarStar.pde
P AvatarStar_vi_vn.pde
U AvatarStar.pde\scripts\sys\deltaProbe.lua
U AvatarStar_vi_vn.pde\scripts\sys\deltaProbe.lua

Text thuần, cắt theo \n. Mỗi dòng biến thành một node 0x21c byte, trong đó byte đầu là chữ opcode còn đường dẫn bắt đầu từ offset 2. Nói cách khác dòng nào cũng có dạng cứng: một chữ cái, một dấu cách, rồi đường dẫn.

Có năm opcode:

Op Nghĩa Applier thật sự làm gì
P <archive> mở archive ra làm mục tiêu đăng ký tên archive. Từ đó op nào có đường dẫn bắt đầu bằng <archive>\ sẽ áp bên trong archive đó thay vì ghi ra đĩa
A <path> thêm CreateDirectoryA tạo thư mục nếu thiếu, rồi mở, cấp phát, đọc hết, ghi hết
U <path> ghi đè y hệt A nhưng bỏ bước tạo thư mục
D <path> xóa DeleteFileA
S <number> tiến độ atoi con số rồi ghi vào một biến global đếm tiến độ

Cả hai đều chép nội dung file y hệt nhau. A chỉ khác ở chỗ gọi thêm CreateDirectoryA trước khi chép. Nên chọn A khi đường dẫn là mới toanh, chọn U khi thư mục đã có sẵn.

Đo thật: một dòng A trỏ vào /mesh/prop/deltatest/ làm số thư mục trong AvatarStar.pde nhảy từ 946 lên 947, còn số file từ 20.227 lên 20.229.

U lên một path đã có để lại đúng một entry. Nó thay tại chỗ chứ không đẻ ra bản trùng nằm cạnh bản cũ.

Thiếu dòng P AvatarStar.pde thì AvatarStar.pde\weapon\smg_92.lua.cache không còn là đường dẫn bên trong archive nữa, nó thành đường dẫn file trên đĩa và client sẽ tạo ra một thư mục tên AvatarStar.pde.

Định tuyến chạy bằng cách tra đường dẫn, không phải theo P gần nhất. Client lấy thành phần đầu của đường dẫn so với danh sách archive đã đăng ký: trùng thì chui vào archive, không trùng thì ghi ra thư mục client. Nhờ vậy U client.exe ghi thẳng ra đĩa mà không cần đóng khối P nào lại.

B, C, E tới O, Q, R, T, và mọi chữ nằm ngoài khoảng AU đều rơi vào nhánh default rồi bị bỏ qua, không báo một tiếng nào. Không có dialog, không có log.

Applier chép byte, hết. Nó không bọc lại file vào container .cache, cũng không tính hay kiểm idHash nào cả.

Nghĩa là file thuộc họ có bọc container phải được bọc sẵn, kèm idHash đúng cho đường dẫn đích, từ lúc đóng gói delta. Applier sẽ vui vẻ cài một container có hash sai, rồi tới lượt engine lặng lẽ từ chối asset đó lúc load. Không ai báo ở khâu nào cả. Xem Thêm một khẩu súng để biết cách kiểm trước khi ship.

Client tải delta.dat về, đọc patch.lst, rồi thi hành đúng từng dòng. Nó không so gì hết, cũng không kiểm tra xem file cũ có giống như ta tưởng không.

Cho nên chọn cái gì vào danh sách là việc của phía ta. diff_pde.py lo phần đó: so hai bản build của cùng một archive rồi in ra danh sách op.

Chỗ này có một cái bẫy. Mỗi delta được dựng cho đúng một bước nhảy version, từ bản X sang bản Y. Client đang ở bản W cũ hơn X vài bản mà vớ phải delta này thì một dòng A có thể trỏ vào entry nó đã có, còn một dòng U lại trỏ vào entry nó chưa có. Chưa ai đo xem client xử lý hai trường hợp đó ra sao. Nhà phát hành né bằng cách nối 61 block liên tiếp, SrcVer của block sau bằng đúng DstVer của block trước, nên delta nào cũng rơi xuống đúng bản nguồn của nó.

So hai archive 1,27 GB trong 6,4 giây nghe khó tin, nhưng có lý do.

Archive mã hóa bằng một pad cố định 4096 byte căn theo page, tức là page nào cũng là plaintext XOR pad[0:4096], nằm ở đâu trong file cũng vậy. Hai page trùng ciphertext khi và chỉ khi trùng plaintext. Muốn biết một file có đổi hay không thì so ciphertext là đủ, khỏi giải mã. Chỉ page directory với FAT mới phải giải, và cũng chỉ để đi cây thư mục.

Con số 6,4 giây là trường hợp xấu nhất về mặt băm, vì không có gì đổi nên file nào cũng phải băm hết, nhưng lại là trường hợp tốt nhất về I/O vì cùng một file và page cache còn nóng. Một cặp file lạnh thật sự là khoảng 2,5 GB đọc từ đĩa. Cứ ước theo tốc độ đĩa, đừng lấy con số này ra dùng.

Mỗi lần U ghi đè một entry, applier viết chuỗi page mới xuống cuối file rồi bỏ luôn chuỗi cũ. Không có free list, không tái sử dụng. Đo trên một lần chạy thật:

Kích thước file Page địa chỉ được Page còn đi tới được Page mồ côi
AvatarStar.pde bản ship 1.270.980.608 309.995 309.995 0
sau khi thay 2 mesh 1.272.340.480 310.326 310.049 277 (1,1 MB)

283.920 byte nội dung mới làm file phình thêm 1.359.872 byte, gấp khoảng bốn tới năm lần.

Delta vẫn đáng dùng, 320 KB trên đường truyền thì vẫn hơn 1,27 GB. Nhưng thỉnh thoảng phải rebuild archive bằng một bản Replace đầy đủ, coi như bảo trì định kỳ. Muốn biết đã đến lúc chưa thì đi cây thư mục, đếm page còn đi tới được, rồi so với tổng số page địa chỉ được.

Phần lớn việc nội dung không đụng tới kênh patch. Chỉ số vũ khí, giá, quest, buff, danh mục shop đều nằm trong các bảng SYS_* mà server đọc từ database. Sửa chúng thì chạy SQL rồi restart service, người chơi không phải tải gì.

Kênh patch chỉ cần khi đụng tới text, icon, model, map, hoặc chính client.exe.

Nhớ cái ranh giới này lúc lên kế hoạch. Ví dụ ở trang Thêm một khẩu súng chia đúng như vậy: art của khẩu súng đi qua delta, dòng trong shop đi qua SQL.

Bản nháp đang review. Nội dung nội bộ, đừng chia sẻ ra ngoài nhóm.