아크 서버 플러그인 개발 기초 - ArkServerAPI 구조 완전 해설 (Load·Unload·명령어 등록) 🦖
목차11개 항목
안녕하세요! Mojangcam입니다 😎
예전에 아크 서바이벌 플러그인 만들기 - 명령어 글을 올렸는데, 지금까지도 가장 꾸준히 읽히는 글 중 하나예요. 그만큼 아크 서버 운영하시는 분들이 "내 서버에만 있는 기능"을 많이 원하시는 거겠죠.
근데 막상 따라 하다 보면 "그래서 이 코드는 왜 이렇게 생겼지?" 가 헷갈리세요. Load()는 뭐고 DllMain은 왜 있는지, 명령어는 왜 세 종류나 되는지 말이에요.
그래서 오늘은 복붙 전에 플러그인 하나의 골격을 통째로 보여드릴게요. 이 구조만 머릿속에 들어가면 그다음부터는 기능만 갈아끼우면 돼요 🙌
※ 이 글은 ARK: Survival Evolved(서바이벌 이볼브드) + 윈도우 서버 기준입니다.

▲ 폴더명과 .dll 이름이 같아야 하고, 서버 시작 로그에 플러그인이 잡히면 성공입니다
🧩 ArkServerAPI가 뭔가요?
아크 서버는 마인크래프트의 Bukkit처럼 공식 플러그인 API가 없어요. 그래서 커뮤니티가 만든 게 바로 ArkServerAPI입니다.
- 서버 실행 파일에 끼어들어 게임 내부 함수를 후킹(Hook) 해주는 프레임워크예요.
- 플러그인은 C++로 작성해서 .dll로 빌드합니다. (파이썬·자바 아니에요!)
- 현재 GameServersHub에서 유지보수하고 있고, MIT 라이선스 오픈소스입니다.
- Studio Wildcard 공식 프로젝트가 아니에요. 팬 프로젝트라는 점은 알고 쓰셔야 해요.
📌 준비물
- 운영체제: Windows 7 / Windows Server 2008 이상 (리눅스 서버는 미지원)
- Microsoft Visual C++ 2019 재배포 패키지 (x64) — 이거 안 깔면 서버가 튀어요
- Visual Studio 2019 이상 + "C++를 사용한 데스크톱 개발" 워크로드
- ArkServerAPI 본체 — GitHub 저장소 또는 GameServersHub에서 다운로드
- 테스트용 아크 전용 서버 (운영 서버에 바로 올리지 마세요!)
설치는 압축 푸는 게 끝이에요.
# API 설치: 압축 파일 내용을 이 경로에 풀기
\ARK\ShooterGame\Binaries\Win64\
# 플러그인 설치: 이 경로에 플러그인 폴더를 통째로 넣기
\ARK\ShooterGame\Binaries\Win64\ArkApi\Plugins\⚠️ 가장 많이 틀리는 부분: .dll 파일 이름과 상위 폴더 이름이 반드시 같아야 해요. Plugins\HelloPlugin\HelloPlugin.dll 이런 식으로요.
📌 1단계 - 플러그인 폴더 구조 파악하기
완성된 플러그인 하나는 보통 이렇게 생겼습니다.
ArkApi\Plugins\HelloPlugin\
├─ HelloPlugin.dll ← 빌드 결과물 (폴더명과 동일!)
├─ PluginInfo.json ← 플러그인 메타정보 (버전·의존성)
└─ config.json ← (선택) 설정값로그는 여기서 확인합니다. 문제 생기면 제일 먼저 여는 파일이에요.
- 일반 로그 :
\ARK\ShooterGame\Win64\logs\ArkApi.log - 크래시 로그 :
\ARK\ShooterGame\Saved\Logs
📌 2단계 - 플러그인의 뼈대: DllMain → Load → Unload
여기가 오늘의 핵심이에요. 플러그인은 결국 DLL 하나라, 서버가 이 DLL을 메모리에 올릴 때와 내릴 때 호출되는 지점이 있습니다.
BOOL APIENTRY DllMain(HMODULE, DWORD ul_reason_for_call, LPVOID)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 플러그인이 메모리에 올라갈 때
Load();
break;
case DLL_PROCESS_DETACH: // 내려갈 때 (서버 종료 등)
Unload();
break;
}
return TRUE;
}하나씩 뜯어볼게요 💡
- DllMain : 윈도우가 DLL을 로드/언로드할 때 불러주는 표준 진입점이에요. ArkServerAPI가 따로 만든 게 아니라 윈도우 규격입니다.
- DLL_PROCESS_ATTACH : 붙을 때 한 번. 여기서
Load()를 불러 초기화합니다. - DLL_PROCESS_DETACH : 떼어질 때 한 번. 여기서
Unload()로 등록한 걸 전부 되돌려야 해요. - 이 짝을 안 맞추면 리로드할 때 명령어가 중복 등록되거나 후킹이 남아서 서버가 튀어요.
그리고 Load() 안에서 제일 먼저 해야 하는 건 로거 초기화입니다.
Log::Get().Init("HelloPlugin"); // 플러그인 전용 로거 생성
Log::GetLog()->info("HelloPlugin 로드 완료");📌 3단계 - 명령어 3종 등록하기 (채팅·콘솔·RCON)
ArkServerAPI는 명령어를 세 가지 통로로 나눠서 받아요. 세 개의 함수 시그니처가 서로 다르니 이건 꼭 외워두세요.
| 종류 | 등록 함수 | 누가 쓰나 |
|---|---|---|
| 채팅 명령어 | AddChatCommand("/hello", &Fn) |
게임 안 플레이어 |
| 콘솔 명령어 | AddConsoleCommand("Hello", &Fn) |
서버 콘솔·관리자 |
| RCON 명령어 | AddRconCommand("Hello", &Fn) |
외부 RCON 툴·디스코드 봇 |
각각의 콜백 시그니처는 이렇게 생겼어요.
// 채팅
void Fn(AShooterPlayerController* pc, FString* message, EChatSendMode::Type mode);
// 콘솔
void Fn(APlayerController* pc, FString* message, bool write_to_log);
// RCON
void Fn(RCONClientConnection* conn, RCONPacket* packet, UWorld* world);하나씩 뜯어볼게요 💡
- AShooterPlayerController* : 명령어를 친 플레이어예요. 여기서 스팀 ID·캐릭터·레벨을 다 끌어올 수 있습니다.
- FString* : 플레이어가 입력한 메시지 전체예요.
/give 5같은 인자를 파싱할 때 쓰죠. - EChatSendMode::Type : 전체채팅인지 부족채팅인지 구분입니다.
- RCONClientConnection* : 응답을 되돌려줄 연결 객체.
conn->SendMessageW(packet->Id, 0, &reply)로 답장을 보냅니다.
메시지 보내는 방법도 두 가지로 나뉩니다.
SendChatMessage(pc, 보내는사람, 내용): 채팅창에 대사처럼 표시SendServerMessage(pc, 색상, 내용): 화면 중앙에 서버 메시지로 표시 (FColorList::Green등)
📌 4단계 - PluginInfo.json 작성하기
플러그인 폴더에 같이 넣는 메타정보 파일이에요. 이게 없거나 형식이 틀리면 로드가 안 됩니다.
{
"FullName": "Hello Plugin",
"Description": "첫 플러그인 예제",
"Version": 1.0,
"MinApiVersion": 3.5,
"Dependencies": []
}- Version : 내 플러그인 버전
- MinApiVersion : 이 플러그인이 요구하는 최소 API 버전. 서버의 API가 이보다 낮으면 로드를 거부해요. (위 값은 예시니, 본인 서버에 깔린 API 버전에 맞춰주세요)
- Dependencies : 먼저 로드돼야 하는 다른 플러그인 (예:
["Permissions"])
권한 관리가 필요한 플러그인이라면 Permissions 플러그인을 의존성으로 걸고 Permissions::IsPlayerHasPermission(steam_id, "내플러그인.권한이름") 형태로 체크하면 돼요.
📌 전체 코드 (복붙 OK)
// ArkServerAPI · ARK: Survival Evolved (Windows) 전용
// Visual Studio 2019+ / 구성: Release / 플랫폼: x64 / 구성 종류: 동적 라이브러리(.dll)
#include <API/ARK/Ark.h>
#include <API/UE/Math/ColorList.h>
#pragma comment(lib, "ArkApi.lib")
// ---------- 채팅 명령어: /hello ----------
void HelloChatCommand(AShooterPlayerController* player_controller, FString* /*message*/,
EChatSendMode::Type /*mode*/)
{
if (ArkApi::IApiUtils::IsPlayerDead(player_controller))
return;
const uint64 steam_id = ArkApi::IApiUtils::GetSteamIdFromController(player_controller);
Log::GetLog()->info("/hello 사용자 SteamID: {}", steam_id);
ArkApi::GetApiUtils().SendChatMessage(player_controller, L"서버",
L"안녕하세요! 플러그인이 정상 동작 중입니다.");
}
// ---------- 콘솔 명령어: Hello ----------
void HelloConsoleCommand(APlayerController* player_controller, FString* /*message*/, bool /*write_to_log*/)
{
auto* shooter_player = static_cast<AShooterPlayerController*>(player_controller);
ArkApi::GetApiUtils().SendServerMessage(shooter_player, FColorList::Green,
L"HelloPlugin 작동 중!");
}
// ---------- RCON 명령어: Hello ----------
void HelloRconCommand(RCONClientConnection* conn, RCONPacket* packet, UWorld* /*world*/)
{
FString reply = L"HelloPlugin 작동 중!";
conn->SendMessageW(packet->Id, 0, &reply);
}
// ---------- 플러그인 진입점 ----------
void Load()
{
Log::Get().Init("HelloPlugin");
ArkApi::GetCommands().AddChatCommand("/hello", &HelloChatCommand);
ArkApi::GetCommands().AddConsoleCommand("Hello", &HelloConsoleCommand);
ArkApi::GetCommands().AddRconCommand("Hello", &HelloRconCommand);
Log::GetLog()->info("HelloPlugin 로드 완료");
}
// ---------- 플러그인 종료점 (등록한 건 반드시 되돌리기) ----------
void Unload()
{
ArkApi::GetCommands().RemoveChatCommand("/hello");
ArkApi::GetCommands().RemoveConsoleCommand("Hello");
ArkApi::GetCommands().RemoveRconCommand("Hello");
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Load();
break;
case DLL_PROCESS_DETACH:
Unload();
break;
}
return TRUE;
}빌드 설정은 이 네 가지만 맞추면 됩니다.
- 구성 종류 : 동적 라이브러리(.dll)
- 플랫폼 : x64 (x86으로 빌드하면 로드 자체가 안 돼요)
- 추가 포함 디렉터리 : API의
include폴더 - 추가 라이브러리 디렉터리 : API의
lib(또는out_lib) 폴더
💡 언어 표준은 C++17 이상으로 잡아두는 게 안전해요. API 헤더가 최신 문법을 써서 구버전 표준에선 컴파일 에러가 쌓입니다.

▲ 구성 종류(.dll)·플랫폼(x64)·포함 디렉터리·라이브러리 디렉터리 네 곳만 맞추면 됩니다
📌 실행 결과
빌드한 HelloPlugin.dll과 PluginInfo.json을 ArkApi\Plugins\HelloPlugin\ 에 넣고 서버를 재시작해보세요. (플러그인은 핫 리로드가 안 돼요. 서버를 다시 올려야 합니다.)
- 로그에
HelloPlugin 로드 완료가 찍혔다면 성공! - 게임 채팅창에
/hello를 치면 서버 응답이 돌아옵니다. (스크린샷은 실제 서버에서 찍어 추가할 예정이에요) - RCON 툴에서
Hello를 보내면 동일한 메시지가 답장으로 옵니다.

▲ 문제가 생기면 이 로그 파일부터 여세요 — 로드 여부가 여기 다 찍힙니다
📌 자주 나는 에러
1. 플러그인이 아예 로드가 안 돼요 (로그에 아무 흔적도 없음)
- 원인 ①:
.dll파일명 ≠ 상위 폴더명. 제일 흔한 실수예요. - 원인 ②: 경로가 틀림.
Binaries\Win64\ArkApi\Plugins\아래가 맞는지 확인하세요. - 원인 ③:
PluginInfo.json이 없거나 JSON 문법이 깨짐(마지막 콤마 주의!).
2. 서버가 켜지자마자 튀어요
- 원인 ①: Visual C++ 2019 재배포 패키지(x64) 미설치.
- 원인 ②: 플러그인을 x86으로 빌드함. 반드시 x64여야 해요.
- 원인 ③: 게임 업데이트 직후. 아크가 패치되면 API가 참조하는 메모리 오프셋이 틀어집니다. → API 업데이트가 나올 때까지 서버 자동 업데이트를 잠시 막아두세요. 아크 플러그인 서버 운영의 숨은 복병이에요.
3. LNK2019 - 외부 기호를 찾을 수 없음
- 원인:
ArkApi.lib를 링커가 못 찾는 경우예요. - 해결:
#pragma comment(lib, "ArkApi.lib")가 있는지, 추가 라이브러리 디렉터리에 API의 lib 폴더를 넣었는지 확인하세요.
4. 한글 메시지가 깨져서 나와요
- 원인: JSON 설정 파일에서 읽은 UTF-8 문자열을 그대로 넘겼을 때 생깁니다.
- 해결:
ArkApi::Tools::Utf8Decode(문자열)로 변환해서FString으로 만들어 보내세요.
5. 리로드하니 명령어가 중복으로 등록돼요
- 원인:
Unload()에서Remove...를 빼먹은 경우예요. - 해결:
Load()에서 등록한 명령어·후킹은 1:1로 전부Unload()에서 해제하세요.
💬 마무리
오늘은 아크 플러그인의 골격을 봤어요. 정리하면 딱 세 줄입니다.
- 플러그인은 x64 DLL이고,
DllMain에서Load()/Unload()로 갈라진다 Load()에서 로거 초기화 → 명령어·후킹 등록,Unload()에서 전부 해제- 폴더명과
.dll이름을 맞추고PluginInfo.json을 같이 넣는다
다음 아크 편에서는 커스텀 명령어 확장으로 인자 파싱·권한 체크·설정 파일 연동까지 붙여볼게요. 기대해주세요 🙌
