'마녀:귀찮아!' DevLog 07 — 유저 컨텐츠 보호의 아키텍처
WM에 UGC 샌드박스 검증 시스템을 도입한 기록. 플레이어 생성 컨텐츠를 안전하게 검증하는 설계, 구현, 테스트 전략.
'마녀:귀찮아!' DevLog 07 — 유저 컨텐츠 보호의 아키텍처
머리말
WM에 유저 생성 컨텐츠(UGC) 시스템을 본격 도입하기로 했다. 플레이어가 자신만의 아이템, 스토리, 월드를 만들 수 있게 하면 게임의 생명력이 길어진다. 하지만 유저 데이터는 신뢰할 수 없다 — 악의적이든 실수든, 잘못된 컨텐츠가 게임을 부셔버릴 수 있다.
그래서 검증 시스템을 설계했다. 이 글은 그 과정을 기록한다.
문제: 신뢰할 수 없는 입력
기존에는 개발자가 만든 아이템, NPC, 스토리만 로드했다. 데이터는 모두 git에 있고, 우리가 검수했다.
하지만 유저 생성 컨텐츠는 다르다:
- 플레이어가 로컬 파일을 직접 수정할 수 있다.
- JSON을 손으로 조작해 불가능한 아이템을 만들 수 있다 (체력 -999, 공격력 999999).
- 게임 엔진이 예상하지 못한 필드가 들어올 수 있다.
결론: 유저 데이터는 철저히 검증해야 한다.
설계: 3단계 방어선
Phase A: 스키마 검증
먼저 JSON 구조 자체가 게임 도메인 모델과 맞는지 확인한다.
UGCDataSO(ScriptableObject): WM의 아이템/NPC/스토리 데이터 정의UGCJsonSerializer: JSON을 읽을 때 스키마 체크- 필수 필드 누락? → 거부
- 예상치 못한 필드? → 경고 (향후 호환성)
- 필드 타입 불일치? → 거부
1
2
3
4
5
6
7
8
9
10
11
12
13
// 의사코드
public bool ValidateSchema(JObject userJson)
{
foreach (var field in domainSchema.Fields)
{
if (field.IsRequired && !userJson.ContainsKey(field.Name))
return false; // 필수 필드 누락
if (userJson[field.Name].Type != field.Type)
return false; // 타입 불일치
}
return true;
}
Phase B: 시드 데이터 로딩
공식 샘플 데이터(seed data)를 먼저 로드해 기준을 정한다.
SeedDataSO: 게임에서 “정상” 데이터의 범위 정의SeedLoader: 샘플 JSON을 읽고 범위 저장- 아이템 가격 범위: 10~10000
- 스텟 범위: 체력 1~100, 공격력 0~50
유저 데이터는 이 범위 내에 있어야 한다.
Phase C: 샌드박스 검증
실제 유저 파일을 로드할 때, 위 기준들을 적용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public bool ValidateUGCData(UGCData userData)
{
// 1. 스키마 검증
if (!ValidateSchema(userData.Json))
return false;
// 2. 범위 검증 (시드 기준)
if (userData.ItemPrice < seedData.MinPrice ||
userData.ItemPrice > seedData.MaxPrice)
return false; // "해킹된" 가격
// 3. 논리 검증
if (userData.MagicCost > userData.ManaCap)
return false; // 불가능한 마나 소비
return true;
}
구현 세부
파일 구조
1
2
3
4
5
6
7
8
9
10
11
12
Assets/
├── Domain/SDK/
│ ├── UGCJsonSerializer.cs # JSON 파싱 + 스키마 검증
│ ├── UGCDataSO.cs # 데이터 정의
│ └── ...
├── Domain/
│ ├── SeedDataSO.cs # 시드 데이터 저장소
│ └── SeedLoader.cs # 시드 로딩 로직
└── Tests/
├── UGCSerializerTests.cs
├── SandboxValidationTests.cs
└── ... (18개 단위테스트)
테스트
- Roundtrip 테스트: JSON → Object → JSON가 같은가?
- 스키마 테스트: 잘못된 필드는 거부하는가?
- 샌드박스 테스트: 해킹된 값(체력 -1, 공격력 9999)은 거부하는가?
- 호환성 테스트: 향후 필드 추가 시 기존 파일도 로드 가능한가?
총 18개 테스트, 모두 통과.
배운 점
1. 도메인과 직렬화를 분리하자
처음에는 UGCData 클래스 하나에 모든 것을 때려박았다. 검증, 변환, 저장을 섞었다.
나중에 분리했다:
UGCData= 순수 도메인 객체 (검증 로직 X)UGCJsonSerializer= JSON ↔ 객체 변환만SandboxValidator= 비즈니스 규칙 검증
결과: 각각 테스트 가능해졌고, 나중에 XML이나 Binary 형식도 쉽게 추가 가능.
2. 시드 데이터는 관례다
“정상 데이터의 범위”를 정하는 건 코드가 아니라 관례다. 시드 파일에 명시하니 기획자도 이해하고, 데이터 변경도 쉬워졌다.
3. 거부는 명확하게
검증 실패 시 “거부” 또는 “경고”를 명확히 구분했다:
- 거부 (Critical): 게임이 깨질 수 있음 (필수 필드 누락, 타입 불일치)
- 경고 (Warning): 호환성 문제일 수 있음 (예상치 못한 필드)
로그를 이렇게 나누니 디버깅이 훨씬 명확해졌다.
다음
- Phase D: 런타임 상호작용 검증 (플레이어가 생성한 아이템을 실제로 사용할 때 추가 검증)
- Phase E: 플레이어 에디터 UI (JSON 수정 대신 게임 안에서 아이템 생성)
- 공식 샘플 라이브러리 배포 (플레이어가 쉽게 시작할 수 있는 템플릿)
메모
- 참고: JSON Schema (Draft 7) 스펙 비교 — WM의 간소화된 접근과 차이
- 키워드: data validation, domain-driven design, test-driven development, asmdef isolation
- 다음 편 아이디어: 플레이어가 만든 데이터를 어떻게 게임에 반영할지 (로딩, 캐싱, 버전 관리)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

