İçeriğe geç

Metin2 Source'a Sistem Eklerken Sık Yapılan Hatalar

Hazırlayan: Metin2VIP Editörleri Yayın: 10 dk okuma

Kısaca: Metin2 source'a sistem eklerken en sık hata, değişikliği zincirin bir halkasında bırakmaktır. Paket yapısını değiştiren bir bayrak hem sunucunun service.h dosyasında hem istemcinin Locale_inc.h dosyasında açılmalı, common klasöründeki bir başlık değiştiyse game ile db birlikte derlenmelidir. Yeni paket iki tarafta aynı numara ve aynı boyla, #pragma pack(1) bölgesinde tanımlanır; yeni quest fonksiyonu questlua dosyasındaki tabloya ve quest_functions listesine, istemci tarafı Python modül tablosuna ve app sabitlerine eklenir.
İçindekiler
  1. Bir sistem kaynakta nerelere dokunur?
  2. Hata 1: Bayrak iki tarafta aynı değil
  3. Hata 2: Paket numarası ve boyu iki tarafta farklı
  4. Hata 3: game ve db birlikte derlenmedi
  5. Hata 4: Enum sırası kaydı, elle yazılmış sayılar kaldı
  6. Hata 5: Proto sütunları her yolda aynı değil
  7. Hata 6: Yeni quest fonksiyonu kayda girmedi
  8. Hata 7: Python tarafı eksik kaldı
  9. Sistem sökerken geride kalanlar

Metin2 source'a bir sistem eklerken yapılan hataların çoğu aynı kökten gelir: değişiklik zincirin bir halkasında unutulur. Paket yapısını değiştiren bir bayrak sunucunun service.h dosyasında açılıp istemcinin Locale_inc.h dosyasında kapalı kalır, ortak bir başlık değişir ama yalnız game yeniden derlenir, yeni paket bir tarafta 14, öbür tarafta 20 bayt olur. Aşağıda bu kaynakta bir sistemin dokunduğu yerler ve her hata için gerçek dosya ve fonksiyon örnekleri var.

Derleme adımları Metin2 source derleme, klasörlerin görevi Metin2 source yapısı yazısında. Bu kaynaktaki bayrakların tam listesi service.h özellikleri yazısında.

Bir sistem kaynakta nerelere dokunur?

Örnek olarak hedef penceresinde canavarın düşürdüklerini gösteren sistem (ENABLE_TARGET_INFORMATION_SYSTEM) alınsın. Bayrak, tanımlandığı iki dosyanın dışında sunucuda 8, istemcide 9 dosyada geçer:

Katman Sunucu İstemci
Bayrak common/service.h:58 UserInterface/Locale_inc.h:67
Paket numarası game/src/packet.h:74-76 (CG 89), :272-274 (GC 139) UserInterface/Packet.h:119-121, :330-332
Paket yapısı packet.h:1473-1487 Packet.h:1993-2006
Boy kaydı packet_info.cpp:235-236 PythonNetworkStream.cpp:101-102
İşleyici input_main.cpp:3420-3421, CInputMain::TargetInfoLoad PythonNetworkStreamPhaseGame.cpp:399-400
Python'a açılış net.SendTargetInfoLoad (PythonNetworkStreamModule.cpp:1791-1793), app.ENABLE_TARGET_INFORMATION_SYSTEM (PythonApplicationModule.cpp:1696-1700)
Arayüz root/uitarget.py:610

Sistemin oyun mantığı (char.cpp, item_manager.cpp) bunların üstüne gelir. Bir sistemi başka bir kaynaktan taşırken bu satırların her birinin karşılığı aranır. Biri eksik kaldığında derleme çoğu zaman yine geçer, hata çalışırken çıkar.

Hata 1: Bayrak iki tarafta aynı değil

Sunucunun bayrakları Serversrc/Server/common/service.h, istemcininkiler Clientsrc/Client/UserInterface/Locale_inc.h dosyasındadır; paket şifrelemesi bayrağı (_IMPROVED_PACKET_ENCRYPTION_) istemcide ayrıca EterBase/ServiceDefs.h içindedir. İki dosyada aynı adla 18 bayrak geçer ve uyuşmazlığın etkisi bayrağın neyi değiştirdiğine bağlıdır:

  • Paket yapısını değiştirenler (ENABLE_VIEW_TARGET_MONSTER_HP, ENABLE_EXTENDED_ITEMNAME, ENABLE_PLAYER_PER_ACCOUNT5): bir tarafta kapalıysa paket boyları tutmaz, bağlantı kopar.
  • Sayı sırasını değiştirenler (ENABLE_MOUNT_COSTUME_SYSTEM, ENABLE_ACCE_SYSTEM): bir enum'un ortasına değer ekledikleri için sonraki numaralar kayar, giyili eşya başka yuvada görünür.
  • Yalnız gösterimi değiştirenler: bu kaynakta ENABLE_MAGIC_REDUCTION_SYSTEM sunucuda açık (service.h:41), istemcide yorum satırında (Locale_inc.h:40). Efsun numarası (APPLY_RESIST_MAGIC_REDUCTION = 98) iki tarafta elle yazıldığı için paket kaymaz; ama istemci bu efsunu ipucu tablosuna eklemez (uitooltip.py:448-451) ve Büyü Bozma Taşı gibi taşların bonus satırı görünmez.

Hata 2: Paket numarası ve boyu iki tarafta farklı

Çekirdek bir paketin boyunu paketten okumaz. İlk bayttaki numaraya bakar, boyu packet_info.cpp'deki tablodan alır (CInputProcessor::Process, input.cpp:62-90). İstemci 20 bayt gönderip sunucu 14 bayt beklerse kalan 6 bayt bir sonraki paketin başı sanılır; orada tanınmayan bir numara bulunur, syserr'e UNKNOWN HEADER: %d, LAST HEADER: %d(%d), REMAIN BYTES: %d, fd: %d düşer ve bağlantı kapanır. LAST HEADER ve parantezdeki boy, en son doğru sanılan paketi gösterir; boyu yanlış tanımlanan paket çoğu zaman odur. İstemcideki karşılığı Unknown packet header: %d, last: %d %d satırıdır, istemci bunu yazıp kendini kapatır (PythonNetworkStream.cpp:547-553).

Yeni bir paket eklerken dört şey denetlenir:

  1. Numara iki tarafta boş olmalı. Sunucunun CPacketInfo::Set fonksiyonu aynı numaranın ikinci kaydını sessizce atlar, ilk kayıt geçerli kalır (packet_info.cpp:21-25). İstemcinin tablosu ise üstüne yazar, son kayıt geçerli olur (EterLib/NetPacketHeaderMap.cpp:4-7). Aynı numarayı iki pakete vermek iki tarafta farklı boylar demektir.
  2. Yapı #pragma pack(1) bölgesinde olmalı. Bölge sunucuda packet.h:342'de açılıp dosyanın sonunda (:2457) kapanır, istemcide Packet.h:453-454 ile :2948 arasındadır. Kapanış satırının altına eklenen yapıya derleyici dolgu koyar: TPacketGCTargetInfo (1 + 4 + 4 + 4 + 1 bayt) paketli hâlde 14, dolguyla 20 bayttır.
  3. Tipler iki tarafta aynı boyda olmalı. Sunucu -m32 ile, istemci Win32 olarak derlenir; long iki tarafta 4 bayttır. MSVC'de time_t 8 bayt olduğu için istemci UserInterface/StdAfx.h:11-13'te _USE_32BIT_TIME_T tanımlar; TPacketGCTime (packet.h:1972-1976) ancak böyle tutar. Sunucunun paketli bölgesinde 61 satırda long alan var. "Source'u 64 bit derlemek" bu yüzden -m32'yi silmekle bitmez: 64 bit Unix'te long 8 bayta çıkar ve bu paketlerin hepsi istemciyle uyuşmaz. Yeni pakette DWORD, int gibi boyu sabit tipler kullan.
  4. Boy tablosuna kayıt eklenmeli. Sunucuda packet_info.cpp'ye bir Set satırı, istemcide PythonNetworkStream.cpp'ye STATIC_SIZE_PACKET ya da DYNAMIC_SIZE_PACKET türüyle bir Set satırı yazılır.
// game/src/packet_info.cpp:235-237
#ifdef ENABLE_TARGET_INFORMATION_SYSTEM
	Set(HEADER_CG_TARGET_INFO_LOAD, sizeof(TPacketCGTargetInfoLoad), "TargetInfoLoad", false);
#endif

Hata 3: game ve db birlikte derlenmedi

common klasöründeki başlıklar iki çekirdeğin ortak dilidir; service.h de length.h, item_length.h ve tables.h üzerinden ikisine birden girer. db açılışta protoları oyun çekirdeğine gönderirken her tablonun yapı boyunu da yazar (db/src/ClientManager.cpp:447-453). Oyun çekirdeği bunu kendi boyuyla karşılaştırır, tutmazsa mob table size error ya da item table size error yazıp kapanır (CInputDB::Boot, game/src/input_db.cpp:560-587). İki çekirdeğin syslog'unda sizeof(TMobTable) = %d satırları da vardır; yan yana koymak hangi yapının farklı olduğunu gösterir.

Paket numaraları da ortaktır. ENABLE_CHANNEL_SWITCH_SYSTEM, tables.h:101-103'te HEADER_GD_FIND_CHANNEL = 99 ekler. Eski bir db bu paketi tanımaz ve Unknown header (header: %d handle: %d length: %d) yazar (ClientManager.cpp:2879); ters durumda oyun çekirdeği in InputDB: UNKNOWN HEADER satırını basar (input_db.cpp:2569). Harita sınırı MAP_ALLOW_LIMIT (32) bile TPacketGDSetup yapısının boyunu belirler (tables.h:804).

Oyun çekirdekleri arasındaki P2P paketlerinde de kural aynı: ENABLE_CMD_WARP_IN_DUNGEON, TPacketGGWarpCharacter yapısına mapIndex alanı ekler (packet.h:488-497). Bütün çekirdekler aynı share/bin/game dosyasını çalıştırdığı sürece sorun yoktur; bir çekirdeğe ayrı ikili koymak bu paketleri bozar.

Ortak bir başlık değiştiyse en güvenli yol üst klasörde gmake çalıştırmaktır; varsayılan hedef kütüphaneleri, game'i ve db'yi sırayla temizleyip derler (Serversrc/Server/Makefile:10). Sonra iki ikili birlikte yerine konur, bütün çekirdekler birlikte yeniden başlatılır.

Hata 4: Enum sırası kaydı, elle yazılmış sayılar kaldı

Bayraklar çoğu zaman bir enum'un ortasına değer ekler. Giyilme yuvaları iyi bir örnektir (common/length.h:144-155):

	WEAR_COSTUME_BODY,	// 19
	WEAR_COSTUME_HAIR,	// 20
#ifdef ENABLE_MOUNT_COSTUME_SYSTEM
	WEAR_COSTUME_MOUNT,
#endif
#ifdef ENABLE_ACCE_SYSTEM
	WEAR_COSTUME_ACCE,
#endif
	WEAR_BELT,			// 23

Üç kostüm bayrağı açıkken kemer 23, silah kostümü 24 numaradır. Binek kostümü kapatılırsa kuşak 21'e, kemer 22'ye iner. Veritabanı giyili eşyanın yerini bu numarayla saklar (item_manager.cpp:437-438), yükleme de aynı numarayla EquipTo çağırır (input_db.cpp:1570-1573). EquipTo yalnız numaranın sınır içinde ve yuvanın boş olduğuna bakar, eşyanın türüne bakmaz (item.cpp:958-979); kayıtlı kemer bu durumda silah kostümü yuvasına oturur.

İstemci de bazı yuvaları elle yazar: kuşak c_Costume_Slot_Start + 3, kemer c_Equipment_Start + 23 (GameType.h:72-76, :110-117). Satırın yanındaki Korece not bunu açıkça söyler: "Dikkat, 19 sayısı elle yazıldı. Sunucuda kostüm yuvaları şu an 19'dan başlıyor; sunucunun common/length.h dosyasındaki EWearPositions sırasına bak." Bayrak kapatınca bu sabitler kendiliğinden değişmez.

Ortak yapılar da enum'a bağlı olabilir. TItemAttrTable dizisinin boyu ATTRIBUTE_SET_MAX_NUM'dır (tables.h:690-706). ENABLE_ITEM_ATTR_COSTUME bu enum'a üç değer ekler (length.h:657-663), db de item_attr sorgusuna costume_body ve costume_hair sütunlarını katar (ClientManagerBoot.cpp:622-630). Bu kaynakta kapalı olan bayrak açılır da tablo bu sütunları taşımazsa sorgu sonuç döndürmez, db no result from item_attr yazıp açılmaz.

Bazı sabitler SQL sorgularında da elle yazılmıştır. Soket sayısı ITEM_SOCKET_MAX_NUM = 3'tür (item_length.h:13); şemada socket0 ile socket5 arası sütunlar bulunur (sql/player.sql:124-129), ama db'nin yükleme ve kaydetme sorguları socket0, socket1, socket2 adlarını tek tek yazar (ClientManagerPlayer.cpp:326, 387, Cache.cpp:94-98, ClientManager.cpp:709, 898, 1439). Sabiti büyütmek bu sorguları ve istemcideki ITEM_SOCKET_SLOT_MAX_NUM'ı (GameType.h:323) değiştirmez. Tabloların genel yapısı Metin2 veritabanları yazısında.

Hata 5: Proto sütunları her yolda aynı değil

db, item_proto.txt'yi Set_Proto_Item_Table ile okur ve 35 sütun bekler (db/src/ProtoReader.cpp:1071); dördüncü sabit efsun bu yapıya sonradan eklenmiştir (ITEM_APPLY_MAX_NUM = 4, item_length.h:12). Ekleme her yola yansımamış: protoları MySQL'den okuyan InitializeItemTableFromDB yalnız üç efsun sütunu seçer (ClientManagerBoot.cpp:1421-1452), tabloyu MySQL'e yansıtan MirrorItemTableIntoDB'nin bir dalı dört, öbür dalı üç efsun yazar (:1175-1220). Bu yolların etkisi proto araçları yazısında.

dump_proto istemcinin ItemData.h ve PythonNonPlayer.h başlıklarını doğrudan içerir (Tools/Proto/dump_proto/dump_proto.h:23-27) ve efsun sütunlarını istemcinin CItemData::ITEM_APPLY_MAX_NUM sabitiyle okur (dump_proto.cpp:547-551). Aracın beklediği sütunlar istemcinin yapısıdır; istemcide yapı değişince araç da yeniden derlenir. Başlıklara ../../../Client/ göreli yoluyla ulaştığı için paketteki Clientsrc/Client düzeninde yol uyarlanmadan derlenmez.

Üretilen dosyanın kayıt boyu istemcinin beklediğinden farklıysa istemci syserr.txt'ye CPythonItem::LoadItemTable: invalid item_proto[%s] STRIDE[%d] != sizeof(SItemTable) yazar ve eşya tablosunu yüklemez (GameLib/ItemManager.cpp:280-289). Bu kaynaktaki ENABLE_PROTOSTRUCT_AUTODETECT üç kayıt boyunu tanır (GameLib/ItemData.h:581-595); yeni bir yapı bu listeye de eklenmelidir. Kısaca yeni bir sütun: db okuyucusu, tables.h, MySQL tablosu ve iki sorgusu, istemci yapısı, dump_proto ve paketleme demektir. Sütunların anlamı item_proto yazısında.

Hata 6: Yeni quest fonksiyonu kayda girmedi

Quest fonksiyonları game/src/questlua_*.cpp dosyalarında ALUA(...) ile yazılır ve dosyanın sonundaki luaL_reg tablosuna eklenmeden Lua'ya görünmez. ENABLE_D_NJGUILD ile gelen lonca zindanı girişi buna örnektir:

// game/src/questlua_dungeon.cpp:1759-1764
#ifdef ENABLE_D_NJGUILD
	// d.new_jump_guild(map_index, x, y)
	{ "new_jump_all_guild",		dungeon_new_jump_guild	},
	{ "new_jump_guild",			dungeon_new_jump_guild	},
#endif
	{ "new_jump_party",		dungeon_new_jump_party	},

Tablo AddLuaFunctionTable("d", dungeon_functions) ile d adıyla kaydedilir (:1803). Yeni bir modül dosyası açılıyorsa üç yer daha gerekir: Register...FunctionTable bildirimi questlua.h:12-36'ya, çağrısı CQuestManager::InitializeLua içine (questlua.cpp:673-698), dosya adı Makefile'daki CPPFILE listesine (game/src/Makefile:121).

Son adım quest klasöründeki quest_functions listesidir; bu pakette 982 satırdır, d.new_jump_guild 100. satırdadır. qc derleyicisi bu listeyi okur, ikilisinde load_quest_function_list ve Calls undeclared function! : metinleri geçer. qc'nin kaynağı pakette olmadığı için kuralın tamamı bilinmiyor: paketteki derlenmiş kuşak quest'i listede olmayan pc.open_sash'ı çağırıyor, yani her eksik ad derlemeyi durdurmuyor. Yeni adı listeye eklemek yine de güvenli yoldur.

Fonksiyon kayda girmemişse quest derlenir, ama çalıştığı anda syserr'e iki satır düşer: Lua'nın kendi iletisi (LUA_ERROR: %s, questlua.cpp:1040) ve kopan quest'in adı, durumu, olayı (LUA_ERROR: quest %s.%s %s, questmanager.cpp:1736). questlua dosyalarında sys_err makrosu QuestError'a yönlendirilir (questlua.cpp:21-26); TEST_SERVER açıksa hata oyuncuya grup sohbetinden error occurred on [fonksiyon:satır] diye de gider (questmanager.cpp:1742-1761). Kullanım quest fonksiyonları yazısında, tam liste quest fonksiyonları listesinde.

Hata 7: Python tarafı eksik kaldı

İstemcide C++ ile Python arasındaki her bağ elle kurulur:

  • Bayrak Python'a PythonApplicationModule.cpp'de app sabiti olarak açılır. Bu kaynakta her bayrağın iki dalı yazılıdır, kapalıyken sabit 0 olur (:1521-1527). Root paketinde app.ENABLE_ACCE_SYSTEM 39 kez geçer ve bu satırlar bu sayede bayrak kapalıyken de hata vermez; yalnız #ifdef dalı yazılırsa kapalı derlemede AttributeError alınır.
  • Yeni bir net fonksiyonu PythonNetworkStreamModule.cpp'deki s_methods tablosuna eklenir (:1791-1793), tablo Py_InitModule("net", s_methods) ile açılır (:1912).
  • Pencere tasarımı uiscript klasöründeki dosyadır ve LoadScriptFile ile okunur (uiacce.py:45). Bu dosyalar kum havuzunda çalışır, yalnız uiScriptLocale, localeInfo, sys, item, app, player ve utils içe aktarılabilir (ui.py:3489). Dosya açılamazsa syserr.txt'ye Failed to load script file : %s yazılır ve istemci kapanır (ui.py:3532-3548).
  • Pencere interfacemodule.py'de içe aktarılıp oluşturulmadıkça açılmaz (kuşak için :49, :275), dosyalar da root paketine girmelidir. Paketleme Metin2 pack dosyaları yazısında.

Sistem sökerken geride kalanlar

Bir sistemi kaldırmak eklemekten zordur, çünkü izleri bayrağın dışına taşar. Kuşak sistemi (ENABLE_ACCE_SYSTEM) bayrak dosyalarının dışında sunucuda 19, istemcide 36 dosyada geçer; bayrağı iki tarafta kapatıp derlemek yetmez:

  • Quest'ler: quest_list'in 33. ve 37. satırları kuşak quest'lerini derler. collection/talk/acce.lua:29 pc.open_sash(true) çağırır ve bu fonksiyon yalnız bayrakla kaydedilir (questlua_pc.cpp:3756-3758); liste temizlenmezse NPC'ye tıklayan oyuncu LUA_ERROR üretir.
  • Yuvalar ve kayıtlar: kuşak yuvası kalkınca kemerin numarası değişir (Hata 4). Kuşak alt türü ise proto okuyucuda bayraktan bağımsız durur (ProtoReader.cpp:259-261); eşyalar okunur ama giyilecek yuvaları yoktur. part_acce sütunu ve log'daki acce tablosu (sql/acce_system/) kalabilir, kod okumadığı için zararsızdır.
  • Tersine yazılmış dallar: item.cpp:548-556'daki yüzük dalı yalnız binek kostümü ve kuşak bayraklarının ikisi de kapalıyken derlenir ve hiçbir başlıkta tanımlı olmayan WEAR_RING1, WEAR_RING2 adlarını kullanır. İki sistemi birlikte kaldırmak bu yüzden derlemeyi bozar. config.cpp:36 ve arena.cpp:1100'deki gibi yorum satırına alınmış #ifdef'ler de kodu bayraktan bağımsız bırakır.

Bayrağın adını iki kaynakta ve root paketinde aramak eksikleri gösterir:

grep -rn "ENABLE_ACCE_SYSTEM" Serversrc/Server Clientsrc/Client
grep -rln "app.ENABLE_ACCE_SYSTEM" pack/root
grep -n "acce" share/locale/turkey/quest/quest_list

Kuşağın oyuncu tarafı kuşak sistemi yazısında.

Hata satırlarının anlamı syserr mesajları yazısında toplanıyor.

Sık sorulan sorular

Sisteme ait bayrağı yalnız service.h'de açsam ne olur?
Bayrak yalnız sunucu kodunu değiştiriyorsa sorun çıkmaz. Paket yapısını ya da istemciyle paylaşılan bir sayı sırasını değiştiriyorsa iki tarafın paket boyları tutmaz; sunucu syserr'e UNKNOWN HEADER yazıp bağlantıyı kapatır, istemci de Unknown packet header satırıyla kendini kapatır.
Yeni paket eklediğim hâlde bağlantı neden kopuyor?
En sık üç neden: numara başka bir pakete verilmiş (sunucu ilk kaydı tutar, istemci sonuncuyu), yapı #pragma pack(1) bölgesinin dışına yazılmış ya da boy tablosuna kayıt eklenmemiş. syserr'deki LAST HEADER değeri boyu yanlış olan paketi gösterir.
Yeni quest fonksiyonunu ekledim, quest neden LUA_ERROR veriyor?
Fonksiyonun ilgili questlua dosyasındaki luaL_reg tablosuna eklenmesi ve çekirdeğin yeniden derlenip yeniden başlatılması gerekir. Tablo kaydı yoksa Lua fonksiyonu nil görür ve quest çalıştığı anda syserr'e LUA_ERROR düşer.
Bir sistemi kaldırmak için bayrağı kapatmak yeter mi?
Yetmez. Sistemin quest'leri quest_list'te kalır, giyilme yuvası gibi sayı sıraları kayar, veritabanındaki kayıtlar eski numaralarla durur. Bu kaynakta binek kostümü ile kuşağı birlikte kapatmak, yalnız o durumda derlenen ve tanımsız yüzük yuvalarını kullanan bir dal yüzünden derlemeyi de bozar.

İlgili sayfalar

Kategori: Server Kurma Kaynak Kod kaynak kod geliştirici source sistem ekleme c++

Bu sayfadaki bilgiler Metin2'nin sunucu yazılımındaki hesaplamalara ve standart oyun dosyalarına dayanır. Oyun dosyalarını değiştiren sunucularda değerler farklı olabilir. İçerikleri nasıl hazırlıyoruz?

Bu sayfa işine yaradı mı?

Giriş yap, oy ver ya da sayfayı kaydet. Okumak için üyelik gerekmez. WhatsApp X'te paylaş
Yanlış ya da eksik bir bilgi mi gördün? Bildir
Neyin yanlış olduğunu, doğrusunun ne olduğunu ve bunu nereden bildiğini yaz. Mesajın editörlere gider; yazı, kontrol edildikten sonra düzeltilir.
Yalnız editör görür.

Tartışma

Kurallar

Henüz yorum yok. Bu konuda bir deneyimin ya da sorun varsa ilk yorumu sen yaz.

Giriş yap ya da kayıt ol, sonra yorum yaz. Metin2VIP üyeliği hiçbir oyun sunucusunun hesabı değildir.