광고 자리 모집 중 · 제휴 문의

스카이림 스크립트 확장(SKSE)과 게임 엔진 후킹 원리: 파피루스(Papyrus) 가상머신 구조, 네이티브 DLL 코드 인젝션 및 Address Library 버전 분리 분석

365Finds Editorial
Wiki 문서 11,430편 수록

내 AI에서 365 Wiki 사용하기

아래 프롬프트를 복사해 평소 사용하는 AI에 붙여넣고 바로 질문하세요.

전체 Skill 다운로드

먼저 프롬프트로 체험해 보세요. 365 Wiki를 자주 쓴다면 전체 365Wiki Skill을 설치해 매번 붙여넣는 수고를 줄일 수 있습니다. 평소 쓰는 AI가 없다면 Gemini나 Microsoft Copilot 웹 버전도 사용할 수 있습니다.

서론: 베데스다 게임 모딩 생태계의 구조적 특징과 엔진의 한계

지난 10여 년간 베데스다 게임 스튜디오(Bethesda)의 크리에이션 엔진(Creation Engine)은——《엘더스크롤 5: 스카이림》(The Elder Scrolls V: Skyrim)과 《폴아웃 4》를 지탱하는 핵심 기술로서——PC 게임 역사상 가장 거대하고 활성화된 커뮤니티 모드(Mod) 생태계를 구축해 왔습니다. 수많은 모더들의 손을 거치며 바닐라 게임은 현대적인 전투 타격감, 실시간 동적 날씨와 광원, 정교한 사용자 인터페이스, 복합적인 물리 시뮬레이션을 갖춘 기술적 역작으로 진화했습니다.

그러나 공식 게임 엔진은 외부 모드 제작에 대해 매우 보수적인 한계선을 설정해 두었습니다. 공식 개발 툴인 크리에이션 키트(Creation Kit)는 샌드박스 격리 기반의 인터프리터 스크립트 언어인 파피루스(Papyrus) 에 절대적으로 의존합니다. 파피루스는 메모리 침범으로 인한 치명적 오류를 방지하는 데는 탁월하지만, 엄격한 실행 속도 제한(타임슬라이스)을 받으며, 물리적 메모리 버퍼나 현대적인 하드웨어 장치 제어, 확장 UI API에 직접 접근할 수 있는 권한이 결여되어 있습니다.

이러한 태생적 한계를 극복하기 위해 리버스 엔지니어링 커뮤니티가 구축한 도구가 바로 스카이림 스크립트 확장기(Skyrim Script Extender, SKSE) 입니다. SKSE는 네이티브 C++ 런타임 인젝터로 동작하여 실행 중인 게임 프로세스의 메모리 공간에 직접 침투하고, 엔진 내부를 후킹하여 파피루스 가상머신에 수백 개의 네이티브 기계어 명령어를 주입하며, 서드파티 DLL 플러그인의 직접 구동을 가능하게 합니다.

SKSE, 파피루스 가상머신, 그리고 런타임 메모리 매핑 간의 상호작용 원리를 파악하는 것은 게임 강제 종료(CTD) 해결, 세이브 파일의 비대화 및 부패 방지, 공식 게임 패치 배포 시의 모드 호환성 유지에 있어 필수적인 지식입니다.

파피루스(Papyrus) 가상머신 구조: 이벤트 루프, 바이트코드 및 타임슬라이스 제어

베데스다는 파피루스를 설계할 당시, 원시적인 연산 속도보다는 '게임 전체의 구동 안정성'을 절대적인 최우선 순위로 설정했습니다:

[모드 스크립트 소스코드 (.psc)] ──> 파피루스 컴파일러 ──> 바이너리 바이트코드 (.pex)
                                                                 │
                                                                 ▼
[Creation Engine 메인 루프]                             [파피루스 가상머신 (VM)]
   1프레임: 화면 렌더링 -> 물리 연산 -> 게임 로직 ───> 할당된 미세 실행 시간 (~1.2 ms)
   2프레임: 화면 렌더링 -> 물리 연산 -> 게임 로직 ───> 바이트코드 명령어 순차 실행
                                                       (시간 초과 시 정지 후 다음 프레임 이월)

1. 바이트코드와 샌드박스 격리 환경

모드 제작자가 .psc 텍스트 파일로 작성한 코드는 공식 컴파일러를 통해 바이너리 .pex 바이트코드로 변환됩니다. 게임 엔진은 이 바이트코드를 CPU에서 직접 네이티브 실행하지 않고 내장된 전용 가상머신(VM)에서 한 줄씩 해석합니다:

2. 협력적 멀티태스킹과 프레임 단위 시간 예산

모드 스크립트의 과부하로 인해 게임의 화면 렌더링 프레임이 떨어지는 것을 방지하기 위해 파피루스는 '협력적 멀티태스킹' 방식을 강제합니다:

SKSE 심층 분석: 프로세스 인젝션, 메모리 후킹 및 네이티브 함수 등록

SKSE는 실행 중인 SkyrimSE.exe(Windows PE 바이너리 포맷)의 메모리 영역을 직접 수정하여 엔진의 제한을 해제합니다.

[skse64_loader.exe 외부 로더]
       │
       ├─ 1. 일시 중단(CREATE_SUSPENDED) 상태로 게임 주 프로세스 생성
       ├─ 2. 원격 스레드로 LoadLibraryW를 호출하여 프로세스 메모리에 skse64.dll 주입
       ├─ 3. 메모리 쓰기 권한 해제(VirtualProtect) 후 진입점 및 가상 함수 테이블(VTable) 패치
       └─ 4. 게임 메인 스레드 재개 (ResumeThread)
               │
               ▼
[SkyrimSE.exe 프로세스 메모리 영역]
   ├── 원본 엔진 코드 (DirectX 렌더러, Havok 물리 엔진, 사운드)
   ├── 파피루스 스크립트 가상머신
   │     └── [후킹된 가상 함수 테이블]: 네이티브 기계어 함수 등록 테이블 (C++ Native)
   └── SKSE 플러그인 관리자
         └── "Data/SKSE/Plugins/" 경로의 모든 서드파티 DLL 로드

1. 로더를 통한 일시 정지 생성과 원격 스레드 인젝션

skse64_loader.exe를 통해 게임을 실행하면 다음과 같은 정밀한 절차가 전개됩니다:

  1. 로더는 윈도우 API인 CreateProcess를 CREATE_SUSPENDED 플래그와 함께 호출하여 게임 프로세스를 생성하되, 메인 스레드의 첫 번째 어셈블리 명령어가 실행되기 전에 정지 상태로 동결합니다.
  2. 로더는 VirtualAllocEx 및 WriteProcessMemory를 통해 동결된 프로세스의 힙 영역에 skse64.dll의 절대 경로를 기록한 뒤, CreateRemoteThread로 LoadLibraryW를 원격 실행하여 SKSE 핵심 라이브러리를 강제 탑재시킵니다.
  3. SKSE는 VirtualProtect를 사용해 읽기 전용 코드 섹션(PAGE_EXECUTE_READ)을 쓰기 가능 상태(PAGE_EXECUTE_READWRITE)로 변경하고, 핵심 진입점에 점프 트램펄린(Trampoline Hook)을 설치한 뒤 ResumeThread로 게임 메인 스레드를 깨웁니다.

2. 네이티브 C++ 기계어 함수 바인딩

인젝션이 완료되면 SKSE는 파피루스 가상머신의 함수 바인딩 테이블을 가로챕니다. 모더가 파피루스 스크립트에서 SKSE 확장 함수(마우스 화면 절대 좌표 호출, 압축 해제된 인벤토리 순회 등)를 호출하면, VM은 느린 바이트코드 해석을 건너뛰고 skse64.dll에 컴파일된 고성능 C++ 기계어 루틴으로 즉각 분기하여 연산 결과를 반환합니다.

바이너리 호환성 위기와 해결책: 공식 업데이트와 Address Library

오랫동안 스카이림 모드 사용자들에게 가장 큰 재앙은 베데스다 공식의 소규모 게임 패치 배포였습니다. 작은 업데이트 하나만 배포되어도 전 세계의 DLL 플러그인이 일제히 작동을 멈추고 튕김 현상을 유발했기 때문입니다.

메모리 오프셋 변동 문제

베데스다가 게임 소스코드 몇 줄을 수정하고 SkyrimSE.exe를 다시 컴파일하면:

[스카이림 1.5.97 버전]                           [스카이림 1.6.640 버전]
오프셋 0x005A3210: GetPlayerCamera() ──재컴파일──> 오프셋 0x005B48F0: GetPlayerCamera()
       ▲                                                    ▲
       │ (하드코딩된 주소 접근으로 치명적 CTD 발생!)           │
[구형 SKSE 플러그인 DLL] ────────────────────────── (Access Violation 0xC0000005)

Address Library와 CommonLibSSE 기술 패러다임

이러한 고질적 취약점을 영구적으로 해결하기 위해 리버스 엔지니어링 커뮤니티는 Address Library for SKSE Plugins 체계를 확립했습니다:

  1. 통합 함수 식별자(ID) 데이터베이스: 역대 출시된 모든 게임 버전의 수만 개 주요 엔진 함수에 대해 버전 불변의 고유 정수 번호(ID)를 부여한 매핑 테이블을 구축했습니다.
  2. 런타임 동적 주소 해석: 현대적인 DLL 플러그인은 16진수 오프셋을 코드에 직접 기입하지 않고, 미들웨어인 CommonLibSSE를 통해 게임 시작 시 데이터베이스에 실시간으로 질의합니다:
    // 플러그인 초기화 중 카메라 관리자 함수의 실제 물리 메모리 주소를 조회
    uintptr_t targetAddress = AddressLibrary::GetAddress(52147);
    
  3. 업데이트 면역성: 공식 게임 패치가 배포되더라도 커뮤니티에서 Address Library 데이터베이스 파일 하나만 갱신하면 수백 개의 개별 DLL 플러그인 수정 및 재컴파일 없이 즉시 호환성을 유지할 수 있습니다.

세이브 파일 구조, 고아 스크립트와 파피루스 스택 덤프(Stack Dump)

스카이림의 세이브 파일 포맷(.ess)의 가장 중요한 기술적 특성은, 세이브 시점에 실행 중이던 파피루스 가상머신의 전체 상태가 세이브 파일 내부에 고스란히 직렬화(Serialization)되어 영구 기록된다는 점입니다.

[.ess 세이브 파일 내부 구조]
  ├── 전역 월드 상태 레코드 (FormID, 날씨, 게임 내 시각)
  ├── 플레이어 소지품 및 진행 중인 퀘스트 단계
  └── [파피루스 가상머신 동결 스냅샷]
        ├── 현재 실행 중인 모든 활성 스크립트 인스턴스
        ├── 실시간 변수 데이터 및 게임 오브젝트 핸들 포인터
        └── 타임슬라이스 할당을 대기 중인 중단 스레드 호출 스택

게임 도중 스크립트 모드를 삭제할 때 발생하는 치명적 결함

진행 중인 세이브 파일에서 스크립트가 포함된 모드를 중간에 삭제할 경우:

  1. 드라이브에서 .pex 파일은 지워지지만, .ess 세이브 데이터 내부에는 해당 스크립트의 활성 인스턴스와 대기 상태의 스레드가 그대로 박혀 있습니다.
  2. 다음번 로드 시 파피루스 가상머신은 세이브 데이터에서 해당 스크립트 인스턴스를 복원하려 시도합니다. 그러나 디렉터리에서 일치하는 .pex 파일을 찾지 못하므로 Papyrus.0.log에 끝없는 오류를 기록합니다.
  3. 이 참조를 잃어버린 스크립트들을 **고아 스크립트(Orphaned Scripts)**라고 부르며, 가상머신은 이를 메모리에서 해제하지 못하고 매 프레임 연산 자원을 낭비합니다. 이는 장기적으로 세이브 파일 비대화(Save Bloat)와 스크립트 렉을 유발하며 마침내 세이브 파일을 완전히 파괴합니다.

가상머신 큐 포화와 스택 덤프 현상

모드 간 충돌로 인해 스크립트가 무한 재귀 루프에 빠지거나 삭제된 오브젝트를 영구히 대기하게 되면 해당 스레드는 제어권을 반환하지 않습니다. 미처리 작업이 가상머신의 안전 임계치(수만 개 이상의 큐)를 초과하면 엔진은 로그에 대규모 **스택 덤프(Stack Dump)**를 쏟아내며, 이는 곧 가상머신의 붕괴와 함께 게임 프로세스의 즉각적인 CTD로 이어집니다.

트러블슈팅 매트릭스: 스크립트 확장, DLL 충돌 및 CTD 정밀 진단

증상아키텍처 관점의 원인진단 확인 방법권장 조치 및 복구 절차
게임 시작 버튼을 누르자마자 즉시 바탕화면 튕김(CTD)특정 DLL 플러그인과 실행 파일(EXE)의 버전 불일치skse64.log 하단에서 로드 실패된 DLL 항목 확인해당 DLL을 최신화하거나 일치하는 Address Library 설치
MCM 메뉴에 "SKSE64가 실행되고 있지 않습니다" 표시Steam 바로가기 등 일반 실행 파일로 켜서 인젝션 누락게임 내에서 ~ 콘솔을 열고 getskseversion 입력Mod Organizer 2를 통해 반드시 skse64_loader.exe로 구동
대화문 선택이 수초씩 늦게 뜨고 조작 반응이 둔화됨파피루스 VM의 큐 적체로 인한 심각한 스크립트 렉Papyrus.0.log에 동일 스크립트 에러 도배 확인과도한 폴링 루프 모드 제거; FallrimTools로 세이브 청소
빠른 이동이나 문을 열고 지역을 이동할 때 간헐적 CTD인젝션된 C++ 네이티브 루틴에서 널 포인터 참조 오류Crash Logger SSE가 생성한 크래시 덤프 로그 확인호출 스택 최상단의 충돌 모듈 특정; 손상된 메시/DLL 교체
세이브 파일 용량이 50MB를 넘고 로딩이 지나치게 김삭제된 모드의 고아 스크립트 인스턴스가 세이브에 누적FallrimTools ReSaver 도구로 해당 .ess 파일 분석미할당 스크립트 인스턴스 일괄 정리; 진행 중 모드 삭제 금지

게임 엔진의 메모리 후킹 원리와 가상머신 작동 메커니즘을 체계적으로 이해하면, 복잡한 모드 충돌 해결은 막연한 재설치가 아닌 정밀한 시스템 엔지니어링 작업으로 전환됩니다. 365Finds의 전문 기술 분석을 토대로 메모리 주소 해석과 스크립트 수명 주기를 제어함으로써 수백 개의 고난도 모드가 결합된 환경에서도 완벽한 안정성을 구현할 수 있습니다.

참고 자료