목차
개요
근래에 들어 마비노기(영웅전 아님)를 하고 있다.
초등학교때부터 해오던 게임이라 특정 기간에는 몰두해서 즐긴다. 이것저것 참고할게 많은 게임이기도 하기에 플레이에 도움을 주는 외부 사이트를 자주 보곤한다. 그러면서 신기한 점은 이러한 외부 사이트 중 몇 가지는 인게임 정보를 어느정도 반영한다는 것이다.
아무래도 코딩이라는 것을 하고 살다보니 그 동작 원리를 무의식적으로 유추해 보곤 하는데 추측하기로서니 인게임 정보 반영은 "공개된 API를 사용하는 것이지 않을까" 싶었다. 하지만 이 글을 작성하는 시점을 기준으로는 마비노기는 공개된 API가 없다. 그렇다면 인게임 정보를 어떻게 반영하는 것일까?
만약 인게임 정보를 반영하는 방식을 알아낸다면 그것을 토대로 무언가 재미난 걸 할 수 있지 않을까?
1. 배경
마비노기 20주년 판타지 파티에서는 올 하반기에나 인게임의 정보를 알 수 있는 OpenAPI가 제공 예정이라는 소식이 들렸다. 그런데 20주년 판타지 파티는 24년 6월 22일에 있었던 것으로 두 달이 채 안 되었다. 내가 봤던 인게임 정보를 제공하는 사이트는 "싴갤러스"라는 사이트다. 이 사이트가 인게임 정보를 제공해주는 원리를 운영자한테 직접 물어보지 않는 이상 알기 어렵겠지만 어떤 사이트에서 흥미로운 사실을 알게되었다.
글을 읽고 그럴싸한데? 라는 생각을 하다가 실제로 검증도 가능할 듯 싶어 똑같이 로그 수집용 캐릭터를 키고 실시간으로 패킷을 캡쳐 해보기로 했다.
2. 데이터 수집에 Sniffing 을 곁들여야하는 나의 게임환경
나는 게임을 할 떈 게이밍용 노트북을 개발할 때는 맥북을 사용한다. 주로 사용하는 쪽은 맥북이기 때문에 문제가 하나 생긴다.
실시간 패킷을 캡처해야되는 대상은 게이밍용 랩탑이라는 점이다.
게이밍용 랩탑에 개발 환경을 셋팅하고 실시간으로 패킷을 캡처하는 코드를 작성할 수도 있지만 그렇게 하기 귀찮다. 즉, 게이밍 랩탑에 개발 환경을 셋팅하기 귀찮아 맥북을 활용해 게이밍 랩탑의 패킷을 보는 것을 목표로 했다.
대학 재학 시절에 정보보안에 관심이 있어 조금의 잡기술들을 익혔다. 앞서 언급한 상황의 해결책으로 ARP Spoofing을 사용했다. ARP Spoofing은 GateWay와 통신중인 타 PC의 ARP Cache 테이블을 오염시켜 패킷의 방향을 공격자 PC를 거쳐 GateWay로 도달하게 만드는 방법이다.
내 환경에 대입해 보자면 대략 다음과 같이 표현할 수 있을 듯 싶다.
과거에는 위와 같은 흐름을 만들기 위해 Arp Spoofing이 지원되는 도구를 사용했지만 지금 사용중인 맥북에는 도구가 실행되지 않았다. 이 문제는 python의 네트워크 패킷을 다룰 수 있게 해주는 라이브러리인 scapy를 이용해 ARP Spoofing을 간단히 재현함으로써 해결했다.
이로써 맥북에서 게이밍 랩탑의 패킷을 볼 수 있는 환경을 구성했다.
3. 실시간 패킷 캡처는 WireShark로
대학 시절에 네트워크 관련 수업을 들으면서 실시간 패킷을 캡처하는데 "WireShark"라는 도구를 잠깐 사용했던 적이 있다. 그 시절 기억을 상기시키며 WireShark를 꺼냈다. 맥북에서 WireShark를 켜두고 잠시 시간이 지나자 패킷이 수집되었다.
무지성으로 관찰하던 도중 TCP 패킷 쪽에서 감이 왔다. 마비노기는 채팅의 용도에 따라서 메시지에 Prefix를 붙여준다. 전체 유저에게 보여주고 싶은 메시지인 경우에는 "ALL_CHANNELS" 같은 것이 붙는 형식이다. 이를 전제로 WireShark의 Follow > TCP Stream을 이용하면 다음과 같이 인게임에서 발생하는 채팅에 관련된 패킷이 노출되는걸 확인할 수 있었다.
여기까지 한것은 "마비노기 서버로 -> 맥북 -> 게이밍 노트북"로 전달되는 패킷이다. 물론 반대의 경우도 가능했다.
이제 마비노기 서버와 통신은 TCP를 사용하며 마비노기 서버의 IP가 무엇인지 알게되었다. 남은 것은 이 정보를 가지고 스크립트를 짜보는것이다.
왜 따로 스크립트를 짜냐고 한다면 WireShark만을 가지고는 무언갈 개발할 수 없으니까..
4. 이제 Script를 작성해보자.
WireShark를 통해 확인할 수 있었던 마비노기 TCP 패킷을 확인하는 스크립트는 어떻게 짤 수 있을까?
ARP Spoofing에 이용했던 Scapy를 이용해 TCP 패킷만 걸러서 해당 데이터를 확인하는 스크립트를 작성하면 된다.
코드 자체는 심플하다.
def showPacket(packet):
try:
if packet[0][1].src == "": # 마비노기 서버 ip가 들어간다.
if packet[Raw]:
if 34 < len(packet[Raw].load) < 300:
hex_value: str = packet[Raw].load.hex()
bytes_obj: bytes = bytes.fromhex(hex_value)
ingame_message: str = bytes_obj.decode("utf-8", errors="ignore")
if ":" in ingame_message:
if "ALL" in ingame_message:
print("[TYPE]", ingame_message)
else:
print(ingame_message)
except Exception as e:
pass
def sniffing(proto):
sniff(filter="host x.x.x.x and tcp", prn=showPacket) # 마비노기 서버 ip가 들어간다.
if __name__ == '__main__':
proto = "tcp"
sniffing(proto)
위 스크립트는 마비노기 서버 IP와 TCP 패킷을 기준으로 패킷의 출발 주소가 마비노기 서버 IP인 것들만 검증하는게 핵심이다.
if 34 < len(packet[Raw].load) < 300:
조건 체크는 임의로 넣은 것이다. TCP 패킷의 Data가 오고가다 보니 적당히 채팅 입력의 크기를 임의로 산정해서 넣어놨다.
hex_value: str = packet[Raw].load.hex()
bytes_obj: bytes = bytes.fromhex(hex_value)
ingame_message: str = bytes_obj.decode("utf-8", errors="ignore")
스크립트를 작성하면서 위 부분에서 조금 애먹었다. 한글 인코딩과 디코딩 문제로 인해 bytes를 decode 하다가 error가 나면 그냥 무시해버렸다. 위와 같이 처리하지 않는다면 Python에서 한글을 decoding 하는데 Exception이 터진다. 전체적으로 한 번 try ~ exception을 걸어줘서 상관없는 부분이 되었지만 한글 디코딩 문제는 골치아프다.
위 스크립트를 통해 마비노기 인게임 메시지를 다음과 같이 볼 수 있게된다.
5. 마치며
과정과 결과가 어떻게 됐든 인게임 데이터를 수집하는 건 이번 시도가 처음이었다. 해놓고 보니 과정에 대한 흡족스러움이 있지만 결과에 대한 아쉬움은 더러 남는다.
"싴갤러스"라는 사이트 처럼 "활용할만한 무언가"가 목표였는데 간단힌 데이터 수집에 그친 셈이기 때문이다."활용할만한 무언가"를 만들어보자에 대해서는 막연히 마비노기 길드 디스코드 채널에 인게임 동기화 채널을 하나 만들어서 놀면 재밌겠다라는 생각이었다. 그러나 디스코에 입력한 메시지를 마비노기 클라이언트를 통해 전달하는 방법이 떠오르지 않아 포기하기로 했다. 이 부분은 뭔가 개인의 숙제로 남겨야 할 듯 싶다.
신기했던 점은 로컬 네트워크이긴 했지만 ARP Spoofing으로 타 PC의 인게임 메시지를 관찰할 수 있었다는 점이다. 게임 쪽은 잘 모르기에 막연히 게임에서 발생하는 모든 데이터는 암호화시키나 정도로만 의문을 가지고 있었는데 이번 사례를 통해 그렇지 않다는 것도 알게되었다.
평소 접할 수 없는 독특한 주제와 도전이었기에 재미도 나름 한 몫 챙겼다.
6. 번외
마비노기 관련 인게임 데이터를 보고있다가 재밌던 점은 맵에서 사라진 NPC들의 대화가 전달된다는 점이었다.
특정 조건을 클리어한 유저에게는 해당 NPC가 보이지 않게 가려만 두는 건가 ?
'개발 노트 > 개발 삽질' 카테고리의 다른 글
Github Action에서 Selenium 실행시키기 (7) | 2024.09.23 |
---|---|
Obsidian 노트를 Tistory에 업로드 시키는 방법 (4) | 2024.09.15 |
ProxySQL을 사용하면 DB Connection을 줄일 수 있다고 ? (0) | 2024.05.04 |
Python으로 Android에 파일을 전송하는 방법 (1) | 2024.04.01 |
Google API 사용없이 Youtube 영상을 mp3로 저장하기 (1) | 2024.03.26 |