상용화를 위한 서버 프로젝트 이슈 정리

옛날에 팀장 할 때 작성했던 보고서.
책으로는 배울 수 없는 것들이니 읽어보시면 도움 될 것 같습니다.

생각보다 양이 많네요.


1. Protocol 설계와 분석

1.1. Keep Alive의 직접 구현과 암호화 처리를 해야 하는 이유

프로토콜 분석을 위한 Keep Alive Packet 조작을 예방 하는 방법

WinSock에서 제공하는 Keep Alive 검사의 경우 외부 조작이 상대적으로 쉽게 되어 보안에 취약해 질 수가 있습니다. 좀더 안전한 구현을 위해서는 Keep Alive 역시 RC5 등으로 암호화 한 Packet을 전송 하는 것이 보다 안전한 방법이 될 것 입니다.

Keep Alive 구현과 서버 기준의 연결 검사

Tick 검사 후 연결 지속 또는 종료 처리를 하는 순서


1.2. Speed Hack 검사를 위한 적용 위치

Speed Hack의 작동 원리

Speed Gear 참고 화면

Cheat Engine 참고 화면

스피드 핵은 시스템의 시간을 배속에 의해 느리거나 빠르게 작동 하도록 매번 설정해 주는 단순한 기능을 가지고 있습니다. 하지만 이로 인해 시스템의 시간 값을 응용한 Tick 간격 처리 프로그램 들은 실행 타이밍이 느리거나 빠르게 작동 될 수 있습니다. 특히 게임 프로그램 에서의 속력 계산 식은 대부분 V = S / T 식에만 근거 하고 있어 이에 영향이 심각해 지게 됩니다.

PING/PONG에 구현된 현재 방식의 문제점 분석

DoS 공격으로 잘못 된 용어를 사용 중인 Speed Hack 검사 코드는 실제 PING Packet을 Client에서 보내지 않도록 조작 할 경우 무력화 시킬 수 있게 됩니다.

Speed Hack 검사 코드의 위치는 Packet Reader의 조립 완성 분기 코드에 있어야 하며 검사 시간은 서버 Tick을 기준으로 최소(0.n or 0.0n) 보다 작거나 최대(Keep Alive Interval+지연 예상 시간) 보다 클 경우를 감지 하여 강제 접속 종료를 시켜야 합니다.


1.3. 보안 강화를 위한 Random Serial Key 활용 방법

Random Serial Key 생성의 목적

Client가 접속 할 때 마다 Server Tick과 Socket(or Index) 번호를 조합 하여 Handshake 과정에서 서로 가지고 있게 되면 접속을 할 때 마다 다른 암호화/복호화 Key를 가질 수 있게 할 수 있다는 것에서 착안 하여 적용 하기 시작 하였습니다. Protocol 분석을 어렵게 만드는 방법 중 한가지 입니다.

연결 정보 요청을 Client에서 먼저 보낼 경우의 문제점

현재 Client 에서는 Network 연결이 이뤄 질 경우 OnConnectServerComplete 함수가 호출 되면서 첫 Handshake 정보를 Server에게 전달 하는 구조가 됩니다. 이러한 경우 사용자에게 Handshake Packet의 분석을 할 수 있는 Timing을 주게 되어 보안에 취약한 설계라고 할 수 있습니다.

서버가 Connection에 대한 감지를 했을 때 처음 해야 할 일

GetQueuedCompletionStatus를 감지 할 경우 Server는 연결 Flag가 도착 했다면 Handshake를 위한 첫 정보를 보내야 하고 이에 대한 응답이 없거나 잘못된 Packet이 도착 할 경우 즉시 Disconnecting 처리를 해야 합니다. 하지만 현재 Server의 경우 Client에서 첫 Packet이 도착 하기만을 기다리게 되어 사용자로 하여금 Protocol 분석을 할 수 있는 Timing을 주게 됩니다.


1.4. Protocol 암호화를 위한 주의 사항

Header와 Data의 안전한 구성

Header NameTypeSize
MarkWORD2 Byte
sizeWORD2 Byte
ptclWORD2 Byte
encryptBYTE1 Byte
CheckSumWORD2 Byte
잘못 된 Protocol Header 구성

현재 Protocol 설계는 전반적으로 struct 구문의 Bit 제한자를 활용 하지 않아 매우 소모적인 형태를 취하고 있습니다. 또한 구성 역시 Unit 식별자가 Header가 아닌 Data에 포함 되어 있어 Packet을 받는 즉시 Unit이 구분된 형태로 Queuing 되지 않아 극단적인 상황에서의 Server의 경우 Packet 처리 이전에 해당 Client가 종료 될 경우 동일한 Unit 식별자의 다른 접속이 이뤄지게 되어 잘못 된 처리를 할 수 있는 상황이 발생 할 수 있습니다.

IOCP의 Connection Key를 Sequence 등을 두어 좀더 명확 하게 관리 하거나 Disconnecting 발생시 Connection Key로 전체 Packet을 정리 하는 방법이 유효 하겠지만 이보다는 Packet Queuing 단계에서의 Unique한 Unit 식별자로 저장을 한 뒤 각 Protocol 처리 함수에서 Client에 대한 Validation 검사를 수행 하는 것이 좀더 효율 적이고 안전한 방법이 될 것 같습니다.

struct ANS_MOVE_PLACE : PACKET {
	enum {
		SUCCESS_MOVE_SECTOR = 0,
		FAIL_MOVE_SECTOR,
		SECTOR_IS_NULL,
		UNKNOWN_MOVE_TYPE,
		TO_HQ_ERROR_QESDESTINATION,
		DEST_SECTOR_IS_NULL,
		DESTINATION_IS_WRONG_POSITION,
		DESTINATION_IS_WRONG_TO_SECTOR_POSITION,
		DESTINATION_IS_WRONG_SECTOR_POSITION,
		DESTINATION_IS_WRONG_TO_VILLAGE,
		HQSECTOR_IS_WRONG, //본부섹터 널일때..에러
	};
	BYTE	ErrCode;

	enum {
		TO_HQ = 1,	///< 본부 이동 : 이동타입만 전달
		TO_SECTOR,	///< 섹터간 이동 : 이동타입과 섹터ID 전달
		TO_VILLAGE	///< 마을간 이동 : 이동타입과 마을ID 전달
	};

	BYTE	MoveType;
	WORD	DestinationID;
	DWORD	SendPacketTime;
	int	MoveUserUkey;	///< 이동한 놈의 유키
	...
};
잘못 된 Protocol Data 구성

위 코드는 Game에서 항상 존재 해야 하는 Unit Key가 Data 부분에 포함 되어 있고 struct 구문의 Bit 제한자를 활용하지 않아 최적화 되지 않은 설계에 해당하는 내용 입니다.

Protocol 번호를 넣을 수 있는 위치
MTU
Built-inPacket HeaderPacket Data
EthernetIPTCPProtocol HeaderCommand HeaderCommand Data
Game Protocol 설계 예제

Packet Header의 경우 size:11, key:14, crypt:3, padding:4 정도의 구성으로 padding에는 쓰레기 값(rand 함수 등)을 넣고 항상 암호화(RC5) 시키는 것을 권장 합니다 또한 암호화 Overhead를 줄이기 위해 Command Header에 Protocol 번호를 넣고 중요성이 높은 것에 한하여 Packet Data를 전체 암호화(RC6) 시키는 것을 권장 합니다.

1.5. Packet Header를 암호화 해야 하는 이유

AES 암호화 대상 Packet Data 적용 목록

CommandLocation
PTCL_REQ_GAME_CONNECTIONGamePacket.h
PTCL_ANS_GAME_CONNECTIONGamePacket.h
PTCL_ANS_CHARACTER_LISTGamePacket.h
PTCL_ANS_LOAD_CHARACTER_DATAGamePacket.h
PTCL_ANS_LOAD_EQUIP_EQUIPMENT_DATAGamePacket.h
PTCL_ANS_QUICK_SLOT_DATAGamePacket.h
PTCL_ANS_LOAD_INVENTORY_DATAGamePacket.h
PTCL_ANS_LOAD_WAREHOUSE_ITEM_DATAGamePacket.h
PTCL_REQ_CHANGE_ITEM_ENHANCEGamePacket.h
PTCL_ANS_CHANGE_ITEM_ENHANCEGamePacket.h
PTCL_REQ_CHANGE_MAIL_DATAGamePacket.h
PTCL_ANS_CHANGE_MAIL_DATAGamePacket.h
PTCL_REQ_SEND_MAILGamePacket.h
PTCL_ANS_SEND_MAILGamePacket.h
PTCL_REQ_BUY_ITEMGamePacket.h
PTCL_ANS_BUY_ITEMGamePacket.h
PTCL_REQ_REBUY_ITEMGamePacket.h
PTCL_ANS_REBUY_ITEMGamePacket.h
PTCL_REQ_SALE_ITEMGamePacket.h
PTCL_ANS_SALE_ITEMGamePacket.h
PTCL_NOTIFY_OBTAIN_COORDINATEGamePacket.h
PTCL_NOFITY_OBTAIN_ITEMGamePacket.h
PTCL_EVENT_GET_ITEMGamePacket.h
PTCL_DUNGEON_REWARD_SELECTGamePacket.h
PTCL_REQ_IDENTIFY_ITEMGamePacket.h
PTCL_EVENT_MY_SKILL_DATAGamePacket.h
PTCL_REQ_BETTING_SHOP_LISTGamePacket.h
PTCL_ANS_BETTING_SHOP_LISTGamePacket.h
PTCL_ANS_BETTING_SHOP_AVERAGE_PRICEGamePacket.h
PTCL_REQ_BETTING_SHOP_INSERTGamePacket.h
PTCL_ANS_BETTING_SHOP_INSERTGamePacket.h
PTCL_REQ_BETTING_SHOP_DELETEGamePacket.h
PTCL_ANS_BETTING_SHOP_DELETEGamePacket.h
PTCL_REQ_BETTING_MY_LISTGamePacket.h
PTCL_ANS_BETTING_MY_LISTGamePacket.h
PTCL_REQ_BETTING_SHOP_BETTINGGamePacket.h
PTCL_ANS_BETTING_SHOP_BETTINGGamePacket.h
PTCL_REQ_BETTING_SHOP_BUYGamePacket.h
PTCL_ANS_BETTING_SHOP_BUYGamePacket.h
PTCL_REQ_ACHIEVEMENT_GET_REWARDGamePacket.h
PTCL_ANS_ACHIEVEMENT_GET_REWARDGamePacket.h
PTCL_REQ_CHANGE_ITEM_MELTGamePacket.h
PTCL_ANS_CHANGE_ITEM_MELTGamePacket.h
PTCL_REQ_CHANGE_ITEM_COMPOSEGamePacket.h
PTCL_ANS_CHANGE_ITEM_COMPOSEGamePacket.h
PTCL_REQ_CHANGE_ITEM_EXTRACTGamePacket.h
PTCL_ANS_CHANGE_ITEM_EXTRACTGamePacket.h
PTCL_REQ_ITEM_EXTRACTGamePacket.h
PTCL_ANS_ITEM_EXTRACTGamePacket.h
PTCL_REQ_ITEM_SOCKET_INSERTGamePacket.h
PTCL_ANS_ITEM_SOCKET_INSERTGamePacket.h
PTCL_REQ_ITEM_SOCKET_DESTROYGamePacket.h
PTCL_ANS_ITEM_SOCKET_DESTROYGamePacket.h
PTCL_ANS_CHECK_LOAD_CHARACTER_DATAGamePacket.h
PTCL_REQ_ROUTING_CONNECTIONRoutingPacket.h
PTCL_ANS_ROUTING_CONNECTIONRoutingPacket.h
PTCL_REQ_TCLS_LOGIN_INFORoutingPacket.h
PTCL_ANS_TCLS_LOGIN_INFORoutingPacket.h
암호화 되는 Packet Data 목록

XOR 인코딩 대상 Packet Data 적용 목록

CommandLocation
PTCL_REQ_MOVE_PLACEGamePacket.h
PTCL_ANS_MOVE_PLACEGamePacket.h
PTCL_ANS_MAIL_PREVIEW_DATAGamePacket.h
PTCL_ANS_MAIL_DETAIL_DATAGamePacket.h
PTCL_ANS_SERVER_TIMECODEGamePacket.h
PTCL_REQ_NPC_SHOP_ITEM_DATAGamePacket.h
PTCL_ANS_NPC_SHOP_ITEM_DATAGamePacket.h
PTCL_REQ_USE_ITEMGamePacket.h
PTCL_ANS_USE_ITEMGamePacket.h
PTCL_REQ_CHANGE_PARTYGamePacket.h
PTCL_ANS_CHANGE_PARTYGamePacket.h
PTCL_ANS_START_GAMEGamePacket.h
PTCL_ANS_SECTOR_MONSTER_DATAGamePacket.h
PTCL_ANS_SECTOR_MONSTER_DATA_NEWGamePacket.h
PTCL_NOTIFY_ATTACK_RECORDINGGamePacket.h
PTCL_ANS_ATTACK_RECORDINGGamePacket.h
PTCL_EVENT_ACTIVE_SKILLGamePacket.h
PTCL_NOTIFY_BUFF_REMOVEGamePacket.h
PTCL_EVENT_RECOVERY_RESULTGamePacket.h
PTCL_EVENT_RECORDING_RESULTGamePacket.h
PTCL_EVENT_BOSS_CLEAR_RESULTGamePacket.h
PTCL_NOTIFY_USING_SKILLGamePacket.h
PTCL_NOTIFY_KEEP_GUARD_SKILLGamePacket.h
PTCL_ANS_KEEP_GUARD_SKILLGamePacket.h
PTCL_REQ_SEND_CHATGamePacket.h
PTCL_ANS_SEND_CHATGamePacket.h
PTCL_NOTIFY_BUFF_EFFECTGamePacket.h
PTCL_EVENT_BUFF_EFFECTGamePacket.h
PTCL_EVENT_BUFF_ENDGamePacket.h
PTCL_ANS_USER_FRIEND_LOAD_FRIEND_LISTGamePacket.h
PTCL_ANS_USER_FRIEND_LOAD_BLOCK_LISTGamePacket.h
PTCL_REQ_USER_FRIEND_ADD_FRIENDGamePacket.h
PTCL_ANS_USER_FRIEND_ADD_FRIENDGamePacket.h
PTCL_REQ_MAN_TO_MAN_CHATGamePacket.h
PTCL_ANS_MAN_TO_MAN_CHATGamePacket.h
PTCL_REQ_CHANNEL_LIST_STATUSGamePacket.h
PTCL_ANS_CHANNEL_LIST_STATUSGamePacket.h
PTCL_REQ_WALL_BOUND_ADD_DAMAGEGamePacket.h
PTCL_ANS_WALL_BOUND_ADD_DAMAGEGamePacket.h
PTCL_REQ_CHANNEL_LISTRoutingPacket.h
PTCL_ANS_CHANNEL_LISTRoutingPacket.h
인코딩 되는 Packet Data 목록

현재 Protocol 설계의 문제점 분석
Packet Header를 암호화 하는 이유는 Unit Key와 Packet Data의 size, 암호화 type을 숨김으로 서 사용자로 하여금 Protocol 분석을 어렵게 하기 위함 입니다.

현재 Protocol의 경우 Unit Key가 Packet Header가 아닌 Packet Data에 위치 하게 되어 주요 Packet들의 대부분이 암호화 또는 인코딩 처리가 되어 있고 Overhead를 줄이기 위해 이를 Packet Header로 위치 시키게 되면 수정해야 할 대상이 매우 많아지게 됩니다.

위 표는 현재 구현된 암호화 또는 인코딩 될 대상이 되는 Protocol 명령 목록 입니다. 해당 목록 들은 Unit Key가 Packet Header로 옮겨지게 될 경우 Packet Header의 암호화를 통해 발송자를 쉽게 속일 수가 없게 되어 Packet Data를 암호화 할 필요가 없어지게 됩니다. (인증 또는 결제 관련 Packet Data 제외)

또한 Packet Data를 암호화 또는 인코딩으로 전체 처리 할 필요가 없어지게 되어 Overhead가 많이 줄어들게 됩니다.

Game 서버의 목적은 강력한 보안이 아닌 원활 한 Game 서비스에 있으며 이를 위해 최적화 된 설계를 반드시 고려 할 수 있어야 합니다.

1.6. Zombie Client를 고려해야 하는 이유

Game Room 생성시 난입 구현을 해야 하는 이유

방 생성 및 정보 수정 동기화 순서

PTCL_ANS_CHANGE_GAMEDATA가 의미 없이 중복 도착하는 문제가 있습니다.


방 시작 알림과 플레이어 목록 교환


몬스터 정보 교환과 로딩 단계 교환


시작 위치 상태 교환과 활성 캐릭터 정보 교환

현재 Protocol 설계의 경우 초기 난입에 대한 고려가 되어 있지 않아 Multi Play를 위한 대기 시간이 매우 길어지게 됩니다.

온라인 게임의 특성상 모르는 사람과의 Multi Play를 고려 하는 것이 일반적 이며 Loading이 빨리 끝난 Player가 Zombie 또는 Ping이 매우 느린 Player에게 Kick 처리를 할 수 있어야만 의도하지 않은 방이 깨지는 상황들을 피할 수 있습니다.

Party 플레이에 대한 Loading 대기 속도 향상 방법
플레이를 위한 최소 인원이 모이거나 Scene 전환 이후 대기 시간을 두어 Loading이 완료된 플레이어 들을 먼저 시작 시키고 이후 난입 보호 시간들을 정의 하여 초과시 탈락 시키는 방법이 유효 하게 됩니다. 이에 대한 프로토콜 설계도는 예외 처리에 대한 고려 사항들이 많이 포함 되어 있어 문서로 작성시 시간이 많이 길어지게 됩니다.

1.7. 보안 프로그램의 적용 위치

보안 프로그램의 CS검사를 위한 준비 사항
GameGuard를 제외 한 HackShield 또는 XTrap과 같은 대표적인 보안 프로그램의 경우 Client의 Memory Address 변조를 방지 하기 위한 CS검사를 수행 하고 있습니다.

XTrap Map File 추출기

작동 원리는 Client 실행 시 할당 중인 Memory Map을 File로 저장하여 Server에 올리고 특정 영역 들을 돌아가며 Client에서 Send 하여 비교 검사를 수행 하게 됩니다.

이를 위해 확장 가능 한 Protocol 구성이 반영 되어야 하며 로그 처리와 함께 연동 가능 한 Blocking 시스템이 구현 되어야 합니다.

Ping/Pong 구현의 활용
주기적 검사가 실행되는 KeepAlive와 Ping/Pong은 보안프로그램 검사와 함께 처리 할 경우 Packet 감소를 시킬 수 있는 효과를 볼 수 있습니다.

단지 KeepAlive의 경우는 자체 구현이 될 경우 Packet Header만 전송 하게 되어 Ping/Pong Packet 처럼 Protocol 함수 단계까지 내려가지 않아 Ping/Pong Packet 처리에서 주기적인 검사를 포함 시키는 것이 활용 범위가 높아지게 됩니다.

2. 인공지능 설계와 분석

2.1. Client로 구현 된 FSM 구성의 문제점

구현된 State 목록과 Grouping을 해야 하는 이유
분류상태
IDLEFSMS_IDLE
FSMS_QUEST_TARGET_POS
FSMS_WAIT
FSMS_PATROL
FSMS_REACTION
FSMS_WANDER
IDLE or MOVEFSMS_IDLE_MOVE_AI
FSMS_ATTACK_MOVE_AI
FSMS_QUEST_PATH_MOVE
FSMS_RANDOM_MOVE
FSMS_CIRCLE_MOVE
FSMS_LONG_CIRCLE_MOVE
FSMS_TARGET_BACKSIDE_MOVE
ATTACKFSMS_ATTACK_AI
FSMS_SELECT_SKILL
FSMS_SELECT_SKILL1
FSMS_SELECT_SKILL2
FSMS_SELECT_SKILL3
FSMS_SELECT_SKILL4
FSMS_SELECT_SKILL5
FSMS_SELECT_SKILL6
FSMS_SELECT_SKILL7
FSMS_SELECT_SKILL8
FSMS_SELECT_SKILL9
FSMS_SELECT_SKILL10
FSMS_SELECTSKILL_IN_ORDER
FSMS_SELECTSKILL_IN_ORDER1
FSMS_USE_SKILL
FSMS_USE_SKILL_FORCE1
FSMS_USE_SKILL_FORCE2
FSMS_USE_SKILL_FORCE3
FSMS_USE_SKILL_FORCE4
FSMS_USE_SKILL_FORCE5
FSMS_USE_SKILL_FORCE6
FSMS_USE_SKILL_FORCE7
FSMS_USE_SKILL_FORCE8
FSMS_USE_SKILL_FORCE9
FSMS_USE_SKILL_FORCE10
FSMS_DAMAGE
CHASEFSMS_CHASE
FSMS_CHASE_NEAR
FSMS_CHASE_FAR
FSMS_CHASE_X
FSMS_CHASE_Y
FSMS_CHASE_DIRECTION
FSMS_RUSH
FSMS_BOOSTER_DASH
FSMS_BOOSTER_DODGE
RETURNFSMS_ESCAPE
All StateFSMS_DETECT
FSMS_RECOVERY
FSMS_TARGET_RESET
FSMS_RECURRENCE
ATTACK and CHASEFSMS_ESCAPE_WAIT
FSMS_FURYMODE
FOLLOWFSMS_FOLLOW

현재 정의된 복잡한 State의 구조들과 통합 되어야 할 분류 목록

기획자가 작성 해야 할 State 구성들이 매우 복잡한 관계를 가지고 있어 현재 구조로는 Lua Script 환경으로 옮기게 되더라도 생산성 향상이 될 것 같지는 않습니다. 또한 각 동작 별 State로 나누는 것은 State 전환에 걸리는 Overhead를 유발하게 되는 것이어서 NPC 반응 속도를 저하시키는 주요 원인으로 생각 할 수 있습니다.

State 정의의 경우 위 표와 같이 대 분류 정도의 구성으로 각 동작 들은 대 분류 안의 Get/Set 함수들과 조건 분기들로 실행 할 수 있게 하는 것이 작성자로 하여금 코드를 정확하고 빠르게 만들 수 있는 효과를 가져다 주게 됩니다.

State 강제 실행과 Monster 상태 Packet의 조작 위험성
현재와 같이 Client에 FSM이 동작하게 될 경우 외부 조작에 대한 취약성을 드러낼 수 밖에 없으며 Host를 따로 구현 한다 하더라도 Host가 조작이 될 경우 동일 한 취약성을 드러내게 될 것 입니다.

이를 보완 하기 위해서는 NPC Server를 구현 하여 외부 조작에 대한 물리적 안정성을 확보 하는 것이 가장 올바른 방법이라고 생각 합니다.

2.2. Lua Script 기반의 NPC Server 적용 구조

World Server와 NPC Server간의 Packet 중계 위치
대상역할
PlayerConnect
Start
Stop
Delete
Sync
DialogOpen
Close
Message
Quest
ItemShop
Buy
Sell
Pick
Leave
ActorAttack
Move
Change
Quit
OtherAttack
Move
Change
Rebirth
Warp In
Warp Out
Level Up
Sync
NPCStart
Stop
Attack
Move
Change
Forward
Aggro
Recall
Sell
Sync
QuestGive up
Complete
Finish
Use
SkillUse
DOT (Damage Over Time)
WarehouseQuery

Game Client, World Server, NPC Server간의 교환 되어야 할 정보


상황 별 필요한 Data를 기준으로 한 Unit Class 상속 구조

World Server와 NPC Server를 설계 할 때는 필요한 정보를 기준으로 하위 Class를 구현 하는 것이 Packet 낭비와 Memory 낭비를 줄일 수 있는 방법이 됩니다.

Game Client의 경우 Player와 NPC의 전체 정보가 필요 하지만 World Server의 경우 NPC 정보가 일부만 필요하고 NPC Server의 경우 Player의 일부 정보만 필요로 하게 됩니다.

이러한 상황 에서는 상속 설계를 Packet 중계까지 적용 하여 보다 효율 적인 방법으로 구성 하는 것이 올바른 방법이 될 것 입니다.

NPC 서버 에서의 FSM 동작 방식


Lua Script 실행 순서 요약

각 인공지능은 기본 상태(IDLE)로 처음 Loading을 하게 되어 등록 된 State 전환 함수를 이용해 Script 안에서 전환이 이뤄 질 수 있도록 합니다.

NPC Server는 현재 설정된 State를 기준으로 반복적인 호출 하게 하게 되며 상황에 따른 인공 지능을 실행 할 수 있게 됩니다.

실제 구현 코드는 위 순서도 보다 많은 내용들을 담고 있으며 Lua Bind로 작성 하게 됩니다.

Script용 API 함수 등록 방법
module(State) [
	class_<Class Name 1>("Script Class Name 1")
	.def("Script Function Name 1", (Out(Class Name 1::*)(In1)&Class Name 1::Function Name 1)
	.def("Script Function Name 2", (Out(Class Name 1::*)(In1,In2)&Class Name 1::Function Name 2)
	.def("Script Function Name 3", (Out(Class Name 1::*)(In1,In2,In3)&Class Name 1::Function Name 3)
	.def("Script Function Name 4", (Out(Class Name 1::*)(In1,In2,In4)&Class Name 1::Function Name 4)
	.def("Script Function Name 5", (Out(Class Name 1::*)(In1,In2,In5)&Class Name 1::Function Name 5)
	,
	class_<Class Name 2>("Script Class Name 2")
	.def("Script Function Name 1", (Out(Class Name 2::*)(In1)&Class Name 2::Function Name 1)
	.def("Script Function Name 2", (Out(Class Name 2::*)(In1,In2)&Class Name 2::Function Name 2)
	.def("Script Function Name 3", (Out(Class Name 2::*)(In1,In3)&Class Name 2::Function Name 3)
	.def("Script Function Name 4", (Out(Class Name 2::*)(In1,In4)&Class Name 2::Function Name 4)
	.def("Script Function Name 5", (Out(Class Name 2::*)(In1,In5)&Class Name 2::Function Name 5)
];
Lua Script에서 사용 할 함수 등록 방법

2.3. Script용 API 예제

인공 지능용 함수 목록
함수설명
ChangeStateState 전환
ChangeAttackType공격 방식 변경
ClearPathPath Stack 제거
SetPath최 단거리 추적 또는 복귀를 위한 경로 저장
CheckPath경로 상태 확인
Idle휴식 상태로 최소 단위 실행
Return복귀 상태로 최소 단위 실행
Chase추적 상태로 최소 단위 실행
Escape탈출 상태로 최소 단위 실행
Wander배회 상태로 최소 단위 실행
Patrol순찰 상태로 최소 단위 실행
Attack공격 상태로 최소 단위 실행
Dead죽은 상태로 최소 단위 실행
Recall소환 기능 실행
HasEnemyAggro 기준으로 적이 있는지 확인 후 Target 고정
CheckInSight시야 범위 확인
InAttackRange공격 가능 거리인지 확인
IsAlive살아있는지 확인
IsAttacked공격 가능 한지 확인
GiveExpTarget에게 경험치 주기
DropItemItem 떨어뜨리기
LifeHP 설정
Recovery회복 실행
ChangeItemItem 교환
DeleteItemItem 삭제
CheckItem소지한 Item 확인
AwardItemItem 보상
CheckQuestItemQuest Item 확인
SetQuestQuest 설정
StartQuestStepQuest 단계 설정
SetQuestFailQuest 실패
GetQuestStatusQuest 상태 확인
ClearQuestQuest 정보 제거
AwardExp경험치 보상
AwardPoint포인트 보상

Lua Script에서 사용 할 함수 예제

실제 구현 해야 할 Lua Script용 함수 들은 위 목록 보다 많은 양을 가지고 있게 됩니다.

State의 추가와 각 State별 연결 구조


State 구성의 기본 구조

연출 또는 특별한 상황에 따라 기획자 들은 State Type을 추가 하여 ChangeState 함수를 이용 하여 상태 전환을 할 수 있게 됩니다.

Lua Script의 작성 방법
function CHASE_OnEnter()
end

function CHASE_OnUpdate()
	if(false == Npc:IsAlive()) then
		Npc:ChangeState(DEAD, 0);
		return;
	end

	if(FULL == Npc:CheckPath()) then
		Npc:ChangeState(RETURN, 0);
		return;
	end

	if(true == Npc:IsAttacked()) then
		Npc:ChangeState(ATTACK, 0);
		return;
	end

	if(0 == Npc:CheckInSight()) then
		local AttackRange =  Npc:InAttackRange();
		if((-1) == AttackRange) then
			if(false == Npc:Chase()) then
				Npc:ChangeState(RETURN, 0);
				return;
			end
		elseif(1 == AttackRange) then
			Npc:ChangeState(ATTACK, 0);
			return;
		elseif(0 == AttackRange) then
			Npc:ChangeState(RETURN, 0);
			return;
		end
	else
		Npc:ChangeState(RETURN, 0);
		return;
	end
end

function CHASE_OnExit()
end
];
추적 상태에 대한 Lua Script 예제

실제 구현 코드는 위 예제 보다 많은 조건과 실행 함수들이 사용 됩니다.

3. 그 외 미구현 된 기능의 목록

3.1. Hack 방지를 위한 충돌 검사 구현

NTM/NML과 환경 강체 충돌 검사
<LineList>
    <Line PortalId="2" X="100" Y="707" X2="70" Y2="820" TransPos="1" TransOffX="10" TransOffY="0" />
    <Line PortalId="0" X="70" Y="820" X2="700" Y2="835" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="700" Y="835" X2="5434" Y2="820" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="1" X="5434" Y="820" X2="5363" Y2="707" TransPos="1" TransOffX="-10" TransOffY="0" />
    <Line PortalId="0" X="5363" Y="707" X2="5050" Y2="700" TransPos="2" TransOffX="-10" TransOffY="0" />
    <Line PortalId="0" X="5050" Y="700" X2="4953" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="4953" Y="675" X2="4630" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="4" X="4630" Y="675" X2="4470" Y2="675" TransPos="2" TransOffX="0" TransOffY="20" />
    <Line PortalId="0" X="4470" Y="675" X2="4145" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="4145" Y="675" X2="2954" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="2954" Y="675" X2="1670" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="3" X="1670" Y="675" X2="1510" Y2="675" TransPos="2" TransOffX="0" TransOffY="20" />
    <Line PortalId="0" X="1510" Y="675" X2="1205" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="1205" Y="675" X2="610" Y2="675" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="610" Y="675" X2="390" Y2="700" TransPos="0" TransOffX="0" TransOffY="0" />
    <Line PortalId="0" X="390" Y="700" X2="100" Y2="707" TransPos="0" TransOffX="0" TransOffY="0" />
</LineList>
Village Line(NTM) 충돌 예제

Village의 Portal 기능은 클라이언트 에서 이동 정보 전송을 하지 않고 서버에서 충돌 시 이동 결과 값 만을 클라이언트로 전송 하도록 구현 하여야 Warp Hack을 방지 할 수 있습니다.
<LineGroupList>
    <Group ID="group1" ConnectEndLines="1" NoCollision="0">
        <Line X="40" Y="40" />
        <Line X="2060" Y="40" />
        <Line X="2060" Y="1500" />
        <Line X="40" Y="1500" />
    </Group>
</LineGroupList>
Dungeon Line(NML) 충돌 예제

Village의 경우 이동 하는 물체가 없어 환경 강체에 대한 충돌 검사가 필요 없지만 Dungeon의 경우 이를 필요로 하게 됩니다.

환경 강체의 서버 구현은 NPC 들의 부피(공간 분할 단위의 칸 수) 검사와 마찬 가지로 지정된 부피 값을 기준으로 위치 이동을 시키면서 검사 하는 방식을 취하게 됩니다.

충돌 검사 적용 대상
대상역할
ASTAR적 감지 후 최단 거리 길 찾기
Return Path최단 거리 태어난 위치 기록
Path Following최단 거리 주인 위치 기록

충돌 검사가 필요한 기능 목록

움직이는 환경 강체를 고려 하여 최소 단위로 직선 거리를 이동 할 경우 좌표 보정 함수가 필요하게 됩니다.

3.2. 공간 분할과 Packet 중계의 우선 순위 문제

분할 중계 위치 설명


중계 순서 설명을 위한 공간 분할 표

Dungeon에서는 소수 인원에 대한 대상 들이 중계 목표가 되어 위 표와 같은 중계 제한 구성이 필요 없게 되지만 Village에서는 좁은 공간에서 밀집 되어 있는 다수의 Player 들을 고려 하여야만 합니다.

현재와 같이 Sector 안에 들어온 모든 Player 들을 순차적으로 중계만 하게 된다면 모든 Player 들이 표시 되기 전에 Client 들이 죽게 되거나 접속자가 많을 경우 물리적인 자원의 한계 점에 도달 하여 더 이상 게임 진행이 어려운 상황에 직면 할 수도 있게 됩니다.
분할된 공간 에서는 0,0을 기준으로 가시거리까지의 이동 가능 한 피사체 들을 읽어 들여 우선 순위에 따른 제한 된 수만큼의 중계가 이뤄 질 수 있도록 설계 하는 것이 서버 중계의 가장 효과 적인 방법이 될 것 입니다.


중계 우선 순위

3.3. Player Data의 보관 및 저장

보관 및 즉시 저장 항목 위치


즉시 처리 대상과 보관 처리 대상 항목 비교

Player Data와 Point, Item 등의 정보 들은 접속 시 DBMS에서 읽어 들이고 종료 시 DBMS에 저장하는 방식이 DBMS 부하를 줄일 수 있는 유일한 방법이 됩니다.

예외 상황에 대한 Data 손실 상황이 발생 할 경우 해당 정보 들은 Game Log를 바탕으로 복구 시킬 수 있어야 하며 이에 대응 또는 추적 할 수 있을 정도의 Log 내용을 쌓는 것이 작성자가 신경 써야 할 점이 될 것 입니다.

반면 Cash 정보의 경우 민감한 사항이자 IO 부하가 간헐적일 수 박에 없는 내용 이어서 SQL Query 등으로 즉시 저장 하는 것을 권장 합니다.

Mail의 경우 확인 시에만 DBMS에 질의 하고 한번 읽었던 내용은 Cache 하여 다시 DBMS에 질의 하지 안도록 처리 하지 않는 것이 좋습니다. (삭제 등을 하게 되면 빈 공간만큼 추가로 DBMS에서 읽어 들이는 것도 괜찮음)

DB Manager 구현에 대한 의견
DB Manager의 구현은 서버간 이동 시 Disconnecting과 Connecting이 순간 적으로 일어 나게 될 경우 Player의 Data를 Memory에 보관 해 두어 DBMS에 대한 질의 부하를 줄일 수 있게 하는 효과가 있지만 Session 서버를 구현 할 경우 해당 기능은 사용 될 필요가 없게 됩니다.

3.4. NPC에 대한 Host 처리 구현

현재 구현된 Client의 경우 Multi Play에 대한 NPC 인공지능이 서로의 상태 정보를 보정 하고 있어 심각한 수준의 게임 조작 상황에 노출 되어 있습니다.

코드 상으로는 Host 구현이 필요 하다고 생각 하여 준비 하였던 흔적들이 남아 있지만 Host 처리를 위한 필수적인 설계 요소 들이 아직 작성되어 있지 않습니다. (서버 시계를 기반으로 한 각 Client 들의 오차 Tick 만큼의 화면 Skipping 처리 설계 등)

PVE 중심의 게임 특성상 Host 역시 게임 조작을 하게 된다면 심각한 문제 들이 발생 하게 될 것으로 보여지며 NPC Server를 구현 하게 될 경우 Host를 구현 하게 될 때 보다 안정적인 서비스를 구성 할 수 있게 됩니다.

3.5. Multiple NIC에 대한 Bind 처리

Client에게 알리는 Game Server의 IP Address가 첫 번째 NIC의 IP 주소로 전송 되게 되어 n개의 NIC에 대응(0.0.0.0) 할 수 없는 구조 입니다.

Server 쪽에서 IP Address에 대한 설정 파일을 지정하여 Client로 보내는 경우도 중국 서비스와 같은 대형 망 n개:대표 IP n개일 경우를 대응 할 수 없는 방법이 될 것 입니다.

Client의 설정 파일로 Game Server의 접속 IP Address를 지정 하는 것이 가장 편리한 방법이 될 것 입니다.

3.6. 풀기 어려운 암호화 생성 방식 구현


PasswordsPro 참고 화면

인터넷에서 쉽게 구할 수 있는 Decryption 프로그램에 Packet Capture Data를 대입 하거나 시스템 권한으로 접속한 DBMS에서 암호화 문자열을 가져 올 경우 이를 대입하여 사용자 암호를 알아 낼 수 있게 됩니다.

Message Hooking의 경우 Hackshield, Xtrap등의 보안 프로그램 들에 의존 할 수 밖에 없지만 암호화 된 문자열만 가로채는 경우 아래와 같이 쓰레기 값 들을 이용 하여 알려져 있는 Decryption 프로그램 들로부터 이를 보호 할 수 있게 할 수 있습니다.


1Byte 단위로 약속 된 위치에 쓰레기 값 섞기

#define 또는 전역으로 선언 되어 있는 암호화 문자열 들은 함수 안에서 지역 적으로 선언 하게 된다면 해당 인증을 실행 한 후 프로그램에서 즉시 제거 되도록 할 수 있습니다.

암호화 문자열을 Parameter로 전달 해야 할 경우는 되도록 없게 작성 하는 것이 좋으며 필요 하다면 stdcall 보다는 cdecl을 사용 하는 것이 좋습니다.

3.7. Free Server 방지용 서버 인증기 구현


서버 Serial Key 검사 순서도

Authorization Server를 개발사에서 관리 하여 Publisher에 배포 한 각 Daemon들 마다 Serial Key를 발급 하고 이를 검사하여 실행 될 수 있게 하는 구조 입니다.

상용화 시켰던 몇 가지 게임들은 게임 정보 Data 역시 DBMS로 관리 할 수 있게 하여 인증 된 Daemon들 만이 개발사에서 관리 하는 DBMS로 연결 되고 게임 정보 Data를 읽을 수 있게 하였습니다.

OO 서버의 경우 File 기반의 게임 정보 Data를 사용 하고 있어 해당 방식은 사용하지 못할 것으로 생각 됩니다.

3.8. Reconnecting 보완을 위한 Session 서버 구현

Session 서버 에서 처리 될 전체 Command 목록
CommandValueDirection
ROUTING_PROTOCOL_VERSION8Under
DEFINE_LOGIN_PROTOCOL10000Nothing
PTCL_REQ_ROUTING_CONNECTIONUnder
PTCL_ANS_ROUTING_CONNECTIONUpper
PTCL_REQ_TCLS_LOGIN_INFOUnder
PTCL_ANS_TCLS_LOGIN_INFOUpper
PTCL_REQ_SERVER_LISTUnder
PTCL_ANS_SERVER_LISTUpper
PTCL_REQ_CHANNEL_LISTUnder
PTCL_ANS_CHANNEL_LISTUpper
END_LOGIN_DEFINITIONNothing

Session 서버 적용을 위한 Routing Packet 방향 표



CommandValueDirection
RELAY_PROTOCOL_VERSION4Under
START_DEFINE_RELAY_P2P_PROTOCOL1000Nothing
END_DEFINE_RELAY_P2P_PROTOCOL9999Nothing
DEFINE_RELAY_PROTOCOL60000Nothing
PTCL_REQ_RELAY_SERVER_REGISTERUnder
PTCL_ANS_RELAY_SERVER_REGISTERUpper
PTCL_REQ_USER_CREATE_GAMEROOMUnder
PTCL_ANS_USER_CREATE_GAMEROOMUpper
PTCL_REQ_USER_INSERT_GAME_ROOMUnder
PTCL_ANS_USER_INSERT_GAME_ROOMUpper
PTCL_REQ_USER_LEAVE_GAME_ROOMUnder
PTCL_ANS_USER_LEAVE_GAME_ROOMUpper
PTCL_NOTIFY_USER_DISCONNECTUnder
END_RELAY_DEFINITIONNothing

Session 서버 적용을 위한 Relay Packet 방향 표

Relay 서버의 경우 구현이 중단 된 서버 이며 NPC Server로 Host를 대체 하고 Session 서버로 Relay 서버를 대체 할 수 있게 됩니다.




CommandValueDirection
GAME_PROTOCOL_VERSION231Under
START_DEFINE_P2P_PROTOCOL1000Nothing
END_DEFINE_P2P_PROTOCOL9999Nothing
DEFINE_GAME_PROTOCOL20000Nothing
PTCL_REQ_GAME_CONNECTIONUnder
PTCL_ANS_GAME_CONNECTIONUpper
PTCL_REQ_SECOND_PASSWORD_STATEUnder
PTCL_ANS_SECOND_PASSWORD_STATEUpper
PTCL_REQ_RELAY_SERVER_INFOUnder
PTCL_ANS_RELAY_SERVER_INFOUpper
PTCL_NOTIFY_RELAY_CONNECTION_IDUpper
PTCL_REQ_CHARACTER_LISTUnder
PTCL_ANS_CHARACTER_LISTUpper
PTCL_ANS_ERRCODE_CHARACTER_LISTUpper
PTCL_REQ_CHANGE_CHARACTER_SLOTUnder
PTCL_ANS_CHANGE_CHARACTER_SLOTUpper
PTCL_REQ_CREATE_CHARACTERUnder
PTCL_ANS_CREATE_CHARACTERUpper
PTCL_REQ_RESTORE_CHARACTERUnder
PTCL_ANS_RESTORE_CHARACTERUpper
PTCL_REQ_DELETE_CHARACTERUnder
PTCL_ANS_DELETE_CHARACTERUpper
PTCL_REQ_CHANGE_USER_CLASSUnder
PTCL_ANS_CHANGE_USER_CLASSUpper
PTCL_REQ_CHARACTER_LOGOUTUnder
PTCL_ANS_CHARACTER_LOGOUTUpper
PTCL_REQ_LOAD_CHARACTER_DATAUnder
PTCL_ANS_LOAD_CHARACTER_DATAUpper
PTCL_REQ_LOAD_CHARACTER_KEY_CUSTOMIZE_DATAUnder
PTCL_ANS_LOAD_CHARACTER_KEY_CUSTOMIZE_DATAUpper
PTCL_REQ_SAVE_CHARACTER_KEY_CUSTOMIZE_DATAUnder
PTCL_ANS_SAVE_CHARACTER_KEY_CUSTOMIZE_DATAUpper
PTCL_REQ_LOAD_EQUIP_EQUIPMENT_DATAUnder
PTCL_ANS_LOAD_EQUIP_EQUIPMENT_DATAUpper
PTCL_REQ_QUICK_SLOT_DATAUnder
PTCL_ANS_QUICK_SLOT_DATAUpper
PTCL_REQ_LOAD_INVENTORY_DATAUnder
PTCL_ANS_LOAD_INVENTORY_DATAUpper
PTCL_REQ_LOAD_PASSIVE_SKILL_DATAUnder
PTCL_ANS_LOAD_PASSIVE_SKILL_DATAUpper
PTCL_EVENT_PVP_USER_INFOUpper
PTCL_REQ_CHANGE_PASSIVE_SKILL_DATAUnder
PTCL_ANS_CHANGE_PASSIVE_SKILL_DATAUpper
PTCL_REQ_INVENTORY_SORTUnder
PTCL_ANS_INVENTORY_SORTUpper
PTCL_REQ_MOVE_PLACEUnder
PTCL_ANS_MOVE_PLACEUpper
PTCL_REQ_SECTOR_USER_INFOUnder
PTCL_ANS_SECTOR_USER_INFOUpper
PTCL_REQ_USER_JOIN_SECTORUnder
PTCL_ANS_USER_JOIN_SECTORUpper
PTCL_EVENT_USER_JOIN_SECTORUpper
PTCL_REQ_USER_INFO_DETAILUnder
PTCL_ANS_USER_INFO_DETAILUpper
PTCL_EVENT_USER_LEAVE_SECTORUpper
PTCL_REQ_USER_MOVE_STATE_SECTORUnder
PTCL_ANS_USER_MOVE_STATE_SECTORUpper
PTCL_EVENT_USER_MOVE_STATE_SECTOR_FIXUpper
PTCL_REQ_CHANGE_INVENTORY_DATAUnder
PTCL_ANS_CHANGE_INVENTORY_DATAUpper
PTCL_EVENT_EXPIRE_ITEMUpper
PTCL_REQ_CHANGE_QUICK_SLOTUnder
PTCL_ANS_CHANGE_QUICK_SLOTUpper
PTCL_REQ_LOAD_WAREHOUSE_ITEM_DATAUnder
PTCL_ANS_LOAD_WAREHOUSE_ITEM_DATAUpper
PTCL_REQ_CHANGE_ITEM_ENHANCEUnder
PTCL_ANS_CHANGE_ITEM_ENHANCEUpper
PTCL_REQ_MAIL_PREVIEW_DATAUnder
PTCL_ANS_MAIL_PREVIEW_DATAUpper
PTCL_REQ_MAIL_DETAIL_DATAUnder
PTCL_REQ_MAIL_KEEP_DATAUnder
PTCL_ANS_MAIL_KEEP_DATAUpper
PTCL_REQ_CHANGE_MAIL_DATAUnder
PTCL_ANS_CHANGE_MAIL_DATAUpper
PTCL_REQ_SEND_MAILUnder
PTCL_ANS_SEND_MAILUpper
PTCL_EVENT_YOU_HAVE_NEW_MAILUpper
PTCL_REQ_SERVER_TIMECODEUnder
PTCL_ANS_SERVER_TIMECODEUpper
PTCL_REQ_BUY_ITEMUnder
PTCL_ANS_BUY_ITEMUpper
PTCL_REQ_REBUY_ITEMUnder
PTCL_ANS_REBUY_ITEMUpper
PTCL_REQ_SALE_ITEMUnder
PTCL_ANS_SALE_ITEMUpper
PTCL_REQ_USE_ITEMUnder
PTCL_ANS_USE_ITEMUpper
PTCL_REQ_CHANGE_PARTYUnder
PTCL_ANS_CHANGE_PARTYUpper
PTCL_NOTIFY_CHANGE_PARTYUpper
PTCL_EVENT_INVITATION_PARTYUpper
PTCL_EVENT_PARTY_MEMBER_STATEUpper
PTCL_EVENT_PARTY_CHANGE_STATEUpper
PTCL_EVENT_PVP_MEMBER_STATEUpper
PTCL_REQ_NPC_SHOP_ITEM_DATAUnder
PTCL_ANS_NPC_SHOP_ITEM_DATAUpper
PTCL_REQ_USING_EXP_POINTUnder
PTCL_ANS_USING_EXP_POINTUpper
PTCL_REQ_IDENTIFY_ITEMUnder
PTCL_ANS_IDENTIFY_ITEMUpper
PTCL_EVENT_CHANGE_USER_STATEUpper
PTCL_EVENT_CHANGE_USER_ITEMUpper
PTCL_EVENT_CHANGE_USER_ITEM_WITH_OPTIONUpper
PTCL_REQ_PVP_INVITEUnder
PTCL_ANS_PVP_INVITEUpper
PTCL_ANS_PVP_INVITE_BY_USERUpper
PTCL_REQ_PVP_INVITE_REPLYUnder
PTCL_ANS_PVP_RECORDUpper
PTCL_REQ_PVP_RANKLISTUnder
PTCL_ANS_PVP_RANKLISTUpper
PTCL_REQ_ITEM_REINFORCEMENTUnder
PTCL_ANS_ITEM_REINFORCEMENTUpper
PTCL_REQ_CREATE_GAMEROOMUnder
PTCL_ANS_CREATE_GAMEROOMUpper
PTCL_REQ_INSERT_GAMEROOMUnder
PTCL_ANS_INSERT_GAMEROOMUpper
PTCL_REQ_LEAVE_GAMEROOMUnder
PTCL_ANS_LEAVE_GAMEROOMUpper
PTCL_REQ_CHANGE_GAMEDATAUnder
PTCL_ANS_CHANGE_GAMEDATAUpper
PTCL_REQ_GAMEROOMUSER_LISTUnder
PTCL_ANS_GAMEROOMUSER_LISTUpper
PTCL_REQ_LOADING_REPORTUnder
PTCL_ANS_LOADING_REPORTUpper
PTCL_REQ_START_GAMEUnder
PTCL_ANS_START_GAMEUpper
PTCL_REQ_SECTOR_LOADING_REPORTUnder
PTCL_ANS_SECTOR_LOADING_REPORTUpper
PTCL_REQ_SECTOR_MONSTER_DATAUnder
PTCL_ANS_SECTOR_MONSTER_DATAUpper
PTCL_REQ_SECTOR_MONSTER_DATA_NEWUnder
PTCL_ANS_SECTOR_MONSTER_DATA_NEWUpper
PTCL_REQ_START_SECTORUnder
PTCL_ANS_START_SECTORUpper
PTCL_EVENT_CLEAR_SECTORUpper
PTCL_NTY_MAYBE_CLEAR_SECTORUpper
PTCL_NOTIFY_ATTACK_RECORDINGUpper
PTCL_ANS_ATTACK_RECORDINGUpper
PTCL_EVENT_ACTIVE_SKILLUpper
PTCL_NOTIFY_BUFF_REMOVEUpper
PTCL_NOTIFY_BUFF_APPLYUpper
PTCL_EVENT_RECOVERY_RESULTUpper
PTCL_EVENT_RECORDING_RESULTUpper
PTCL_EVENT_BOSS_CLEAR_RESULTUpper
PTCL_NOTIFY_USING_SKILLUpper
PTCL_EVENT_LATENCYUpper
PTCL_NOTIFY_CHARACTER_DEADUpper
PTCL_EVENT_END_BUFF_CHANNELUpper
PTCL_NOTIFY_MONSTER_DATA_INITUpper
PTCL_NOTIFY_OBTAIN_COORDINATEUpper
PTCL_NOFITY_OBTAIN_ITEMUnder
PTCL_EVENT_GET_ITEMUpper
PTCL_NOTIFY_TENDER_ITEMUpper
PTCL_EVENT_TENDER_TABLE_INFOUpper
PTCL_REQ_USE_REBIRTH_COUPONUnder
PTCL_ANS_USE_REBIRTH_COUPONUpper
PTCL_REQ_SUMMON_OBJECTUnder
PTCL_ANS_SUMMON_OBJECTUpper
PTCL_NOTIFY_DUNGEON_REWARD_SELECTUpper
PTCL_DUNGEON_REWARD_SELECTUnder
PTCL_REQ_END_GAMEUnder
PTCL_ANS_END_GAMEUpper
PTCL_EVENT_CHECK_ANTIBOT_DATAUpper
PTCL_EVENT_CHECK_DP_DATAUpper
PTCL_NOTIFY_CHECK_ANTIBOTUpper
PTCL_NOTIFY_CHECK_DPUpper
PTCL_REQ_LOAD_SKILL_DATAUnder
PTCL_ANS_LOAD_SKILL_DATAUpper
PTCL_REQ_GET_SKILLUnder
PTCL_ANS_GET_SKILLUpper
PTCL_EVENT_MY_SKILL_DATAUpper
PTCL_REQ_SKILL_DECK_CHANGEUnder
PTCL_ANS_SKILL_DECK_CHANGEUpper
PTCL_EVENT_AUTO_REFILL_CONTENTSUpper
PTCL_NOTIFY_USER_PC_SPECUpper
PTCL_REQ_LOAD_USER_QUESTUnder
PTCL_ANS_LOAD_USER_QUESTUpper
PTCL_REQ_USER_ACCEPT_QUESTUnder
PTCL_ANS_USER_ACCEPT_QUESTUpper
PTCL_NTY_INFORM_USERQUESTUpper
PTCL_NTY_MODIFY_USERQUEST_STATEVALUEUpper
PTCL_NTY_USERQUEST_SUCCUpper
PTCL_NTY_USERQUEST_FAILEDUpper
PTCL_NTY_USERQUEST_STATEUpper
PTCL_NTY_USERQUEST_STATUSUpper
PTCL_NTY_USERQUESTDIALOG_NEWQUESTUpper
PTCL_NTY_USERQUESTDIALOG_DELETEQUESTUpper
PTCL_REQ_USERQUEST_COMPLETEUnder
PTCL_ANS_USERQUEST_COMPLETEUpper
PTCL_NTY_USERQUEST_ACCEPTRESULTUpper
PTCL_NTY_USERQUEST_COMPLETERESULTUpper
PTCL_REQ_USERQUEST_GIVEUPUnder
PTCL_ANS_USERQUEST_GIVEUPUpper
PTCL_NTY_USERQUEST_TIMEOUT_INFOUpper
PTCL_RELOAD_QUEST_DATAUnder
PTCL_RESET_USER_QUEST_INFOUnder
PTCL_REQ_LOAD_USERQUEST_SELECTEDLISTUnder
PTCL_ANS_LOAD_USERQUEST_SELECTEDLISTUpper
PTCL_NTY_SAVE_USERQUEST_SELECTEDLISTUpper
PTCL_NTY_USER_DAILY_QUESTUpper
PTCL_NTY_USER_QUEST_HISTORYUpper
PTCL_SYSNTY_DEVELOPER_LOGUnder
PTCL_QUEST_USER_ITEM_NTYUnder
PTCL_SYSNTY_GAMEROOM_OBJECT_ADDUnder
PTCL_SYSNTY_GAMEROOM_OBJECT_DELUnder
PTCL_SYSNTY_GAMEROOM_OBJECT_MODUnder
PTCL_NTY_ACQUIRE_QUEST_ITEM_MESSAGEUpper
PTCL_NTY_USER_QUEST_DESTROYUpper
PTCL_NOTIFY_UI_SKILL_CHANGE_STATEUpper
PTCL_NOTIFY_ASSERT_ERRORUpper
PTCL_NOTIFY_P2P_RECV_COUNTUpper
PTCL_NOTIFY_SECTOR_CLEAR_REPORTUpper
PTCL_REQ_CHANGE_TRADE_STATEUnder
PTCL_ANS_CHANGE_TRADE_STATEUpper
PTCL_REQ_CHANGE_TRADE_ITEMUnder
PTCL_ANS_CHANGE_TRADE_ITEMUpper
PTCL_EVENT_TRADE_RESULTUpper
PTCL_NOTIFY_BUSY_NPC_TRADEUpper
PTCL_NOTIFY_PARTY_CHARACTER_STATEUpper
PTCL_AAS_USERDATAUnder
PTCL_NOTIFY_KEEP_GUARD_SKILLUpper
PTCL_ANS_KEEP_GUARD_SKILLUpper
PTCL_REQ_USER_UPDATE_QUESTUnder
PTCL_ANS_USER_QUEST_LISTUpper
PTCL_REQ_USER_QUEST_FAILUnder
PTCL_ANS_USER_QUEST_FAIL_IS_FAILUpper
PTCL_REQ_PARTY_ACCEPT_QUESTUnder
PTCL_REQ_LOAD_SAVE_BUFFUnder
PTCL_ANS_LOAD_SAVE_BUFFUpper
PTCL_NOTIFY_BUFF_EFFECTUpper
PTCL_EVENT_BUFF_EFFECTUpper
PTCL_EVENT_BUFF_ENDUpper
PTCL_ANS_BUFF_ENDUpper
PTCL_NTY_READY_FOR_COMMUNITY_DATA_LOADINGUpper
PTCL_NTY_READY_FOR_GAME_DATA_LOADINGUpper
PTCL_REQ_USER_FRIEND_LOAD_FRIEND_LISTUnder
PTCL_ANS_USER_FRIEND_LOAD_FRIEND_LISTUpper
PTCL_REQ_USER_FRIEND_LOAD_BLOCK_LISTUnder
PTCL_ANS_USER_FRIEND_LOAD_BLOCK_LISTUpper
PTCL_REQ_USER_FRIEND_ADD_FRIENDUnder
PTCL_ANS_USER_FRIEND_ADD_FRIENDUpper
PTCL_REQ_USER_FRIEND_DELETE_FRIENDUnder
PTCL_ANS_USER_FRIEND_DELETE_FRIENDUpper
PTCL_REQ_USER_FRIEND_ADD_BLOCK_FRIENDUnder
PTCL_ANS_USER_FRIEND_ADD_BLOCK_FRIENDUpper
PTCL_REQ_USER_FRIEND_DELETE_BLOCK_FRIENDUnder
PTCL_ANS_USER_FRIEND_DELETE_BLOCK_FRIENDUpper
PTCL_REQ_USER_FRIEND_UPDATE_MEMOUnder
PTCL_ANS_USER_FRIEND_UPDATE_MEMOUpper
PTCL_REQ_USER_FRIEND_UPDATE_BLOCK_MEMOUnder
PTCL_ANS_USER_FRIEND_UPDATE_BLOCK_MEMOUpper
PTCL_NTY_USER_FRIEND_LOGINUpper
PTCL_NTY_USER_FRIEND_LOGOUTUpper
PTCL_NTY_USER_FRIEND_UPDATE_STATEUSUpper
PTCL_REQ_MAN_TO_MAN_CHATUnder
PTCL_ANS_MAN_TO_MAN_CHATUpper
PTCL_NTY_MAN_TO_MAN_CHATUpper
PTCL_REQ_USER_DG_RECORD_LOADUnder
PTCL_ANS_USER_DG_RECORD_LOADUpper
PTCL_NTY_USER_DG_RECORD_UPDATEUpper
PTCL_REQ_USER_DG_OPEN_LOADUnder
PTCL_ANS_USER_DG_OPEN_LOADUpper
PTCL_NTY_USER_DG_OPEN_INSERTUpper
PTCL_REQ_TUTORIAL_START_GAMEUnder
PTCL_ANS_TUTORIAL_START_GAMEUpper
PTCL_REQ_PARTYPR_REGISTER_PR_INFOUnder
PTCL_ANS_PARTYPR_REGISTER_PR_INFOUpper
PTCL_NTY_PARTYPR_REGISTER_PR_INFOUpper
PTCL_REQ_PARTYPR_MODIFY_PR_INFOUnder
PTCL_ANS_PARTYPR_MODIFY_PR_INFOUpper
PTCL_NTY_PARTYPR_MODIFY_PR_INFOUpper
PTCL_REQ_PARTYPR_UNREGISTER_PR_INFOUnder
PTCL_ANS_PARTYPR_UNREGISTER_PR_INFOUpper
PTCL_NTY_PARTYPR_UNREGISTER_PR_INFOUpper
PTCL_REQ_PARTYPR_QUERY_PRLISTUnder
PTCL_ANS_PARTYPR_QUERY_PRLISTUpper
PTCL_REQ_PARTYPR_QUERY_PARTYPR_INFOUnder
PTCL_ANS_PARTYPR_QUERY_PARTYPR_INFOUpper
PTCL_REQ_PARTYPR_JOIN_REQUnder
PTCL_ANS_PARTYPR_JOIN_REQUpper
PTCL_NTY_PARTYPR_RECV_JOIN_REQUpper
PTCL_REQ_PARTYPR_ACCEPT_JOIN_REQUnder
PTCL_ANS_PARTYPR_ACCEPT_JOIN_REQUpper
PTCL_REQ_PARTYPR_REFUSE_JOIN_REQUnder
PTCL_ANS_PARTYPR_REFUSE_JOIN_REQUpper
PTCL_NTY_PARTYPR_JOIN_REQ_ACCEPTEDUpper
PTCL_NTY_PARTYPR_JOIN_REQ_REFUSEDUpper
PTCL_REQ_PARTYPR_JOIN_REQ_CANCELUnder
PTCL_ANS_PARTYPR_JOIN_REQ_CANCELUpper
PTCL_NTY_PARTYPR_JOIN_REQ_CANCELEDUpper
PTCL_REQ_CHANNEL_LIST_STATUSUnder
PTCL_ANS_CHANNEL_LIST_STATUSUpper
PTCL_REQ_CHANGE_CHANNEL_OFFUnder
PTCL_ANS_CHANGE_CHANNEL_OFFUpper
PTCL_RPT_USER_SLEEP_FLAGUnder
PTCL_REQ_USER_IDENTIFYUnder
PTCL_ANS_USER_IDENTIFYUpper
PTCL_REQ_SEND_CHATUnder
PTCL_ANS_SEND_CHATUpper
PTCL_CONSOLE_NOTICE_MESSAGEUnder
PTCL_REQ_GM_CLIENT_CHEAT_KEYUnder
PTCL_ANS_GM_CLIENT_CHEAT_KEYUpper
PTCL_NOTIFY_OTHER_USER_STATE_CHANGEUpper
PTCL_NTY_PINGUpper
PTCL_NTY_PONGUpper
PTCL_REQ_PINGUnder
PTCL_REP_PINGUnder
PTCL_NTY_PING_RECORDUpper
PTCL_REQ_BETTING_SHOP_LISTUnder
PTCL_ANS_BETTING_SHOP_LISTUpper
PTCL_REQ_BETTING_SHOP_AVERAGE_PRICEUnder
PTCL_ANS_BETTING_SHOP_AVERAGE_PRICEUpper
PTCL_REQ_BETTING_SHOP_INSERTUnder
PTCL_ANS_BETTING_SHOP_INSERTUpper
PTCL_REQ_BETTING_SHOP_DELETEUnder
PTCL_ANS_BETTING_SHOP_DELETEUpper
PTCL_REQ_BETTING_MY_LISTUnder
PTCL_ANS_BETTING_MY_LISTUpper
PTCL_REQ_BETTING_SHOP_BETTINGUnder
PTCL_ANS_BETTING_SHOP_BETTINGUpper
PTCL_REQ_BETTING_SHOP_BUYUnder
PTCL_ANS_BETTING_SHOP_BUYUpper
PTCL_RPT_DGRULE_MESSAGEUnder
PTCL_NTY_DGRULE_MESSAGEUpper
PTCL_REQ_MONSTER_RESPAWNUnder
PTCL_ANS_MONSTER_RESPAWNUpper
PTCL_NTY_ACHIEVEMENT_SUCCESSUpper
PTCL_REQ_ACHIEVEMENT_SUCCESS_LISTUnder
PTCL_ANS_ACHIEVEMENT_SUCCESS_LISTUpper
PTCL_REQ_ACHIEVEMENT_GET_REWARDUnder
PTCL_ANS_ACHIEVEMENT_GET_REWARDUpper
PTCL_NTY_MY_MISSION_LISTUpper
PTCL_REQ_CHANGE_ITEM_MELTUnder
PTCL_ANS_CHANGE_ITEM_MELTUpper
PTCL_REQ_CHANGE_ITEM_COMPOSEUnder
PTCL_ANS_CHANGE_ITEM_COMPOSEUpper
PTCL_REQ_CHANGE_ITEM_EXTRACTUnder
PTCL_ANS_CHANGE_ITEM_EXTRACTUpper
PTCL_REQ_ITEM_EXTRACTUnder
PTCL_ANS_ITEM_EXTRACTUpper
PTCL_REQ_ITEM_SOCKET_INSERTUnder
PTCL_ANS_ITEM_SOCKET_INSERTUpper
PTCL_REQ_ITEM_SOCKET_DESTROYUnder
PTCL_ANS_ITEM_SOCKET_DESTROYUpper
PTCL_REQ_WALL_BOUND_ADD_DAMAGEUnder
PTCL_ANS_WALL_BOUND_ADD_DAMAGEUpper
PTCL_REQ_SYNCH_PLAY_START_REPORTUnder
PTCL_ANS_SYNCH_PLAY_START_REPORTUpper
PTCL_REQ_PVP_ROOM_LISTUnder
PTCL_ANS_PVP_ROOM_LISTUpper
PTCL_REQ_JUMP_DG_SECTORUnder
PTCL_ANS_JUMP_DG_SECTORUpper
PTCL_NTY_LUASCRIPT_DEBUG_LOGUpper
PTCL_REQ_CHANGE_SECOND_PASSWORDUnder
PTCL_ANS_CHANGE_SECOND_PASSWORDUpper
PTCL_NOTIFY_CATCH_STATE_CANCELUpper
PTCL_EVENT_SAFE_MODE_STATEUpper
PTCL_USER_SKILL_SOCKET_INFOUnder
PTCL_REQ_WANTED_QUEST_DATAUnder
PTCL_ANS_WANTED_QUEST_DATAUpper
PTCL_REQ_CHECK_LOAD_CHARACTER_DATAUnder
PTCL_ANS_CHECK_LOAD_CHARACTER_DATAUpper
PTCL_REQ_BATTLE_CHANNEL_LISTUnder
PTCL_ANS_BATTLE_CHANNEL_LISTUpper
PTCL_REQ_LIST_ROOMUnder
PTCL_ANS_LIST_ROOMUpper
PTCL_REQ_CREATE_ROOMUnder
PTCL_ANS_CREATE_ROOMUpper
PTCL_REQ_JOIN_ROOMUnder
PTCL_ANS_JOIN_ROOMUpper
PTCL_EVENT_JOIN_ROOMUpper
PTCL_REQ_EXIT_ROOMUnder
PTCL_ANS_EXIT_ROOMUpper
PTCL_EVENT_EXIT_ROOMUpper
PTCL_REQ_CLOSE_SLOT_ROOMUnder
PTCL_ANS_CLOSE_SLOT_ROOMUpper
PTCL_EVENT_CLOSE_SLOT_ROOMUpper
PTCL_REQ_CHANGE_ROOM_TYPEUnder
PTCL_ANS_CHANGE_ROOM_TYPEUpper
PTCL_EVENT_CHANGE_ROOM_TYPEUpper
PTCL_NOTIFY_READY_INFOUpper
PTCL_EVENT_READY_INFOUpper
PTCL_NOTIFY_CHANGE_TEAMUpper
PTCL_EVENT_CHANGE_TEAMUpper
PTCL_REQ_INVITE_USER_LISTUnder
PTCL_ANS_INVITE_USER_LISTUpper
PTCL_REQ_INVITE_USERUnder
PTCL_ANS_INVITE_USERUpper
PTCL_EVENT_INVITE_USERUpper
PTCL_NOTIFY_MOVE_BATTLE_ROOMUpper
PTCL_EVENT_MOVE_BATTLE_ROOMUpper
PTCL_NOTIFY_CHANGE_BATTLE_ROOM_TYPEUpper
PTCL_EVENT_CHANGE_BATTLE_ROOM_TYPEUpper
PTCL_NOTIFY_CHANGE_MAPUpper
PTCL_EVENT_CHANGE_MAPUpper
PTCL_REQ_START_BATTLEUnder
PTCL_ANS_START_BATTLEUpper
PTCL_EVENT_START_BATTLEUpper
PTCL_NOTIFY_BATTLE_SYNCUpper
PTCL_EVENT_BATTLE_SYNCUpper
PTCL_NOTIFY_BATTLE_TRUNUpper
PTCL_EVENT_BATTLE_TRUNUpper
PTCL_EVENT_BATTLE_DEADUpper
PTCL_EVENT_BATTLE_ENDUpper
PTCL_REQ_BATTLE_RANKUnder
PTCL_ANS_BATTLE_RANKUpper
PTCL_REQ_UNBIND_ITEMUnder
PTCL_ANS_UNBIND_ITEMUpper
PTCL_REQ_SHOW_USER_INFOUnder
PTCL_ANS_SHOW_USER_INFOUpper
PTCL_EVENT_SHOW_USER_INFOUpper
PTCL_NOTIFY_SHOW_USER_INFOUpper
PTCL_CTG_EVENT_GET_SHOW_USER_INFOUpper
PTCL_CTG_NOTIFY_GET_SHOW_USER_INFOUpper
PTCL_REQ_SET_AUTOCOMBOUnder
PTCL_ANS_SET_AUTOCOMBOUpper
PTCL_NTY_SET_SECTOR_MANUAL_FINISHUpper
PTCL_NTY_SECTOR_MANUAL_CLEARUpper
PTCL_NTY_SECTOR_MANUAL_FAILUpper
END_GAME_DEFINITIONNothing

Session 서버 적용을 위한 Game Packet 방향 표

총 421개의 Command가 정의 되어 있으며 Request, Response, Broadcast를 묶어서 볼 경우 실제 150여개 정도의 Protocol이 구현 되어 있는 것으로 보여 집니다.

Session 서버를 구현 해야 하는 이유
하나의 World 서버에 접속 할 수 있는 동시 접속 자의 수를 n개의 Session 서버를 이용 해 분산 접속 처리를 할 수 있게 한다면 World 서버의 물리적 최대 접속 자 수를 Socket Overhead를 고려 하지 않고 보다 많은 수를 설정 할 수 있게 됩니다.

또한 통합 서버 등의 서버간 이동이 필요 할 경우에도 Session 서버의 송수신 Direction만을 다른 서버로 변경 할 수 있게 되어 Reconnecting에 대한 부담 또한 줄어 들게 됩니다. (인증 및 계정 정보 보관이 일부 필요해 Player Manager 구현이 필요 함)

서버 대항전 또는 통합 사냥터 등이 구현 될 경우 쾌적한 서비스를 보장 할 수 있게 됩니다.

3.9. Web Launcher와 Normal Launcher를 고려 한 인증 서버 구현

Account 서버의 역할 구성


Web/Normal Launcher 실행 순서와 상황 별 인증 분기

Web Launcher와 Normal Launcher 개발
국가별 요청 사항에 따라 Web 인증을 통하여 Client가 자동 실행 되기를 원하는 곳과 일반 Launcher를 사용 하여 Client에서 ID와 Password을 직접 입력 할 수 있기를 원하는 곳 두 가지가 있어 두 가지 모두 개발 되어야 합니다.

DBMS 인증 방식
Publisher에서 제공 하는 Stored Procedure 또는 지정된 SQL Query 등을 이용하여 인증을 받아오는 방식 입니다.

Protocol 인증 방식
Publisher에서 운영 중인 Account Server로 약속 된 Protocol을 사용 하여 인증을 받아오는 방식 입니다.

3.10. 게임 로그와 운영 도구 개발

Log Format 적용 방법
enum LOG_EVENTS_TYPE {
        LE_SERVER_ERR = 0,
        LE_SERVER_START,
        LE_SERVER_STOP,
        // 서버버그이다. 이메시지가 발생하면, 해당 기능을 재점검할 필요가 있다. LF_AID,LF_ID,LF_STR
        LE_BUG,
        // 매크로(혹은 자체개발한 클라이언트 사용자) LF_AID,LF_ID,LF_STR
        LE_HACK,
        // GameGuard 핵 사용자 LF_AID,LF_ID,LF_DEBUG,LF_STR
        LE_SECURITY,
        // 경험치 정보. LF_AID,LF_ID,LF_EXP,LF_GRADE,LF_POINTS,LF_NEW_EXP,
        // LF_BONUS_EXP,LF_NEW_POINTS,LF_BONUS_POINTS,LF_CONTRIBUTION,
        LE_EXP,
        // 아이템 구매. LF_AID,LF_ID,LF_NEWITEM,LF_TIME,LF_CASH,LF_POINTS,LF_EVENT_CASH,LF_EVENT_POINTS
	LE_ITEM_BUY,
          …
};
Log Event 예제
enum LOG_FIELDS_TYPE {
	LF_NONE = 0,	// (NONE)
	LF_DEBUG,	// Number
	LF_FAIL,		// (NONE)
	LF_SUCCESS,	// (NONE)
	LF_TIME,		// 초
	LF_STR,		// 문자열 -> 변환없음.
	LF_IP,		// IPv4
	LF_PORT,		// port 번호
	LF_EXP,		// 정수형 -> 문자열로 저장한다.
	LF_CASH,		// 정수형 -> 문자열로 저장한다.
	LF_POINTS,	// Point
	LF_EVENT_CASH,
	LF_EVENT_POINTS,
          …
};
Log Field 예제
LOG(
        LE_ITEM_BUY<<
        LF_AID<<GetAid()<<
        LF_NEWITEM<<GetId()<<GetSerial()<<GetStocks()<<
        LF_TIME<<GetTime()<<
        LF_CASH<<GetCash()<<
        LF_POINTS<<GetTotalPoint()<<
        LF_EVENT_CASH<<GetEventCash()<<
        LF_EVENT_POINTS<<GetEventPoint()
);
Game Log 적용 예제

Formatting을 Event와 Field로 분류 하여 ‘,’ 등의 구분 자로 숫자 값을 이용해 저장 한다면 File 크기를 크게 줄일 수 있습니다.

Log 수집 기는 ‘,’ 를 이용한 CSV형태의 Data 일 경우 MS-SQL에서 지원 하는 Script 형식을 사용 하여 작성 하는 것도 좋은 방법이며 Python, PHP, Bash 등의 Script 형식의 코드로 작성 하는 것도 유지 보수에 도움이 됩니다.

Log 수집 기에 의해 각 서버 들의 File Log 들은 특정 위치로 이동이 될 것 이며 모아진 Log 들은 Backup Schedule에 의해 Tarball, Gzip 등으로 압축 하여 보관 하도록 처리 합니다.

댓글

이 블로그의 인기 게시물

개발팀 일정 계획하는 방법

게임을 출시하는 것은 출산의 고통과도 같다.