일부 Steam 게임은 Windows용 전용 서버만 제공합니다. depot에 Linux 바이너리가 없으니 늘 나오는 조언은 "Windows 머신에서 돌려라"입니다. 그럴 필요 없습니다. Wine을 쓰면 Windows 서버를 일반 Linux 머신에서 돌릴 수 있고, Steam에 접속해 실제 플레이어를 받아들입니다. 이것은 예로 Alien Swarm: Reactive Drop(무료 co-op Source 게임)을 사용해 우리가 실제로 성공한 바로 그 레시피입니다. 같은 접근법이 Windows 전용의 다른 Source 전용 서버에도 통할 것입니다.
먼저, 정말로 Linux 서버가 없는지 확인하기
넘겨짚지 마세요. Wine에 의존하기 전에 Steam에 직접 물어보세요. steamcmd를 해당 앱으로 향하게 하고 그 플랫폼 목록을 읽으세요:
steamcmd +login anonymous +app_info_print <appid> +quit \
| grep -iE "oslist|name"서버의 전용 서버 앱이 "oslist windows"뿐이라면, 기댈 네이티브 Linux 빌드가 없습니다. Linux 설치를 강제로 시도해볼 수도 있습니다. 정말로 Linux depot이 없으면 그 자리에서 실패합니다:
steamcmd +@sSteamCmdForcePlatformType linux \
+login anonymous +app_update <appid> validate +quit
# ERROR! Failed to install app '<appid>' (Missing configuration)이 에러와, ELF 파일이 하나도 다운로드되지 않는 것이 그것을 확인해 줍니다. 이제 Wine이 길입니다. 서버와 app 1007(Steamworks Common Redistributables)을 같은 폴더에, 플랫폼 타입 windows로 설치하세요. app 1007은 서버가 필요로 하는 Steam DLL을 가져다주며, 그 이유는 2단계에서 알게 됩니다:
steamcmd +@sSteamCmdForcePlatformType windows +login anonymous \
+force_install_dir /path/to/server \
+app_update <appid> validate \
+app_update 1007 validate \
+quit서버에는 Wine prefix 안에 Microsoft Visual C++ 2019 런타임도 필요합니다. winetricks로 한 번 설치하세요(디스플레이가 필요하므로 xvfb-run으로 감싸세요):
WINEPREFIX=/wine xvfb-run -a winetricks -q vcrun2019
wineserver -wWine으로 순진하게 시도하면 실패하는 이유
서버를 Wine에서 바로 돌리면 연달아 두 개의 벽에 부딪힙니다:
- 콘솔 없음: 서버는 진짜 터미널을 기대합니다. /dev/null에서 리디렉션하면 CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents가 나오고 절대 시작하지 않습니다.
- Steam 라이브러리를 불러올 수 없습니다: 올라온 뒤에도 "Unable to load Steam support library"를 출력하고 Steam에 등록되지 않아, 아무도 찾거나 들어올 수 없습니다.
둘 다 고칠 수 있습니다. 여기 전체 레시피가 있습니다.
1단계: 진짜 터미널(PTY)을 줘라
헤드리스 머신에는 터미널이 없고, Windows 콘솔 서버는 터미널 없이는 실행을 거부합니다. 입력 쪽이 계속 열려 있는 의사 터미널(PTY)이 필요한데, docker run -it가 주는 것이 바로 그것입니다. 흔한 팁은 실행을 script로 감싸는 것입니다. 하지만 터미널이 없는 머신에서는 그게 버그입니다: script 자체의 입력이 파일 끝에 도달하고, 서버의 콘솔 읽기도 그것을 보게 되어 메인 루프가 멈춥니다. 맵은 로드될 수 있지만 서버는 소켓에서 절대 응답하지 않습니다. 대신 PTY를 열고 절대 닫지 않는 작은 Python 래퍼로 시작하고, 가짜 디스플레이로 Xvfb를 쓰세요:
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99 WINEPREFIX=/wine
cat > launch_pty.py <<'EOF'
import os, sys
cmd = ["wine", "srcds_console.exe", "-console", "-condebug",
"-game", "<gamedir>", "+map", "<map>", "-port", "27015"]
pid, master = os.forkpty()
if pid == 0:
os.execvp(cmd[0], cmd)
# Parent: never close master, so the server's input stays open.
# Copy the server output to our own stdout.
while True:
try:
data = os.read(master, 4096)
except OSError:
break
if not data:
break
sys.stdout.buffer.write(data)
sys.stdout.flush()
_, status = os.waitpid(pid, 0)
sys.exit(os.waitstatus_to_exitcode(status))
EOF
python3 launch_pty.py이것만으로도 서버는 맵을 불러오고 게임 포트를 바인딩합니다. 하지만 아직 Steam과 대화하지 못합니다.
2단계: 두 개의 Steam DLL, 두 개의 서로 다른 출처
Source 엔진은 steamclient.dll과 steam.dll 둘 다를 서버 exe 옆에 필요로 하며, 여기 디버깅을 한 바퀴 통째로 더 쓰게 만든 함정이 있습니다: 그것들은 서로 다른 곳에서 오고, app 1007은 그중 하나만 줍니다.
- steamclient.dll: 은 1단계에서 설치한 app 1007(Steamworks Common Redistributables)에서 옵니다. 정식이고, 호환되는 버전이며, Valve가 관리합니다.
- steam.dll: 은 app 1007에 없습니다. 이것은 Steam 클라이언트의 일부이며, 그것을 깔끔하게 얻는 유일한 방법은 Windows용 steamcmd.exe를 Wine 아래에서 한 번 실행하는 것입니다. 그러면 자동 업데이트되어 steam.dll(과 steamclient.dll)을 자기 폴더에 놓습니다.
그러니 Wine 아래에서 steamcmd.exe로 steam.dll을 부트스트랩한 뒤, 두 DLL을 서버 exe 옆에 복사하세요:
# steam.dll: run Windows steamcmd once under Wine (needs Xvfb for a display)
cd "$WINEPREFIX/drive_c/steamcmd"
curl -fsSLO https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
unzip -o steamcmd.zip
DISPLAY=:99 wine steamcmd.exe +quit # lays down steam.dll + steamclient.dll
# place both next to the server exe
cd /path/to/server
cp "$WINEPREFIX/drive_c/steamcmd/steam.dll" .
[ -f steamclient.dll ] || cp "$WINEPREFIX/drive_c/steamcmd/steamclient.dll" .3단계: 왜 steam.dll이 중요한 부품인가
이것은 거의 아무도 적어두지 않는 부분입니다. 에러는 "Unable to load Steam support library"라고 하는데, 여기서 말하는 지원 라이브러리는 steamclient.dll이 아니라 steam.dll입니다. Source 엔진은 Steam 체인 전체를 부트스트랩하기 위해 실행 파일 옆에서 steam.dll을 찾습니다. steamclient.dll만 있는 상태(app 1007이 주는 건 그게 전부입니다)에서는 여전히 실패합니다. steam.dll이 .exe 옆에 놓이는 순간, 로그는 실패에서 성공으로 바뀝니다:
Connection to Steam servers successful.
VAC secure mode is activated.VAC은 서버가 제대로 로그인된 게임서버가 아닌 한 활성화되지 않으므로, 이 줄이 서버가 등록되었다는 증거입니다. 대부분의 Source 서버는 여기서 익명으로 로그인하며 Game Server Login Token(GSLT)이 전혀 필요 없습니다. 일부 게임(CS2 같은)만 하나를 요구합니다.
함정: A2S가 조용해진다
저희가 가장 많은 시간을 날린 함정입니다. script 래퍼를 쓰자 서버가 A2S_INFO에 응답하지 않았습니다. A2S_INFO는 도구가 서버 이름, 맵, 플레이어 수를 읽는 UDP 쿼리입니다. 같은 머신에서 보내도 모든 쿼리가 시간 초과되어 서버가 완전히 죽은 것처럼 보였습니다. 처음엔 Wine을 탓했습니다. 실제로는 Step 1의 멈춘 메인 루프였습니다: 서버가 소켓을 아예 읽지 않았던 것입니다.
PTY를 열어 두자 서버가 A2S에 정상적으로 응답했고 실제 플레이어가 접속할 수 있었습니다. 최종 테스트는 여전히 쿼리 응답이 아니라 클라이언트 접속입니다. PTY가 제대로 되자 실제 플레이어가 접속해 플레이했습니다:
Client "player" connected (10.0.0.5:27005).
1/ 8 on map <mapname>실용적인 교훈: Wine으로 호스팅한 서버가 맵은 로드하는데 A2S가 조용하다면 Wine부터 탓하지 마세요. 콘솔이 어떻게 붙어 있는지 확인하세요. UDP 포트가 바인딩되어 있다고 서버가 살아 있다는 증거는 아닙니다. 멈춘 서버도 포트는 바인딩한 채로 읽지 않은 패킷만 쌓이기 때문입니다.
이게 모든 Windows 게임에서 통할까?
범위는 솔직하게 말씀드립니다. 저희는 Source 엔진 서버에서 이를 증명했고, 같은 방법(PTY, steamcmd로 만든 런타임, exe 옆의 steam.dll)은 다른 Windows 전용 Source 데디케이티드 서버에서도 통할 것입니다. 다른 엔진의 게임은 Steam 연동 방식이 다르므로 별도의 실험으로 다루세요. 어느 쪽이든 예상할 점 하나: 부팅이 네이티브 Linux보다 느립니다(Wine에 steamcmd 단계와 Steam 로그인까지 더해짐).
아니면 이 모든 걸 건너뛰기
오후 하나를 통째로 잡아먹는 종류의 삽질입니다. 서버만 필요하다면 저희가 Linux 서버 군과 Wine 배관을 시간 단위로 대신 운영하며, 첫 1분부터 공인 IP를 드립니다. Alien Swarm: Reactive Drop은 아직 공개 배포 페이지에 없지만, 다른 Windows 전용 서버는 있습니다. 예: Wreckfest 및 rFactor 2. 저희가 제공하는 모든 것 보기: 2hours.gg에서 호스팅하는 것을 보세요.