DLC 다운로드 중 손상됐을 때: 재개·재검증·재다운로드 패키지 무결성 테스트 가이드
DLC 다운로드가 중단되거나 일부 바이트가 손상됐을 때 재개, SHA-256 재검증, 청크 재다운로드가 올바르게 동작하는지 테스트하는 방법을 정리합니다.

핵심 요약
DLC 다운로드 무결성 테스트의 핵심은 부분 파일을 성공으로 처리하지 않고 최종 파일 크기와 SHA-256 해시를 검증하는 것입니다. 다운로드가 중단되면 HTTP Range 요청으로 재개하고 검증에 실패하면 손상된 청크만 재다운로드하거나 전체 파일을 다시 받아야 합니다. 테스트에서는 중단 위치, 파일 절단, 임의 바이트 변조, 서버 응답 불일치, 디스크 부족을 각각 재현해 상태 전이가 정확한지 확인합니다.
DLC 패키지 무결성 검증은 왜 필요한가?
DLC는 본편과 별도로 배포되지만 실행 시 에셋 번들, 압축 패키지, 메타데이터, 라이선스 정보가 서로 일치해야 합니다. 다운로드 중 네트워크가 끊기거나 저장 장치에 쓰기가 실패하면 파일이 존재하더라도 일부 데이터가 빠진 상태가 될 수 있습니다.
따라서 파일 존재 여부만 확인하는 방식은 충분하지 않습니다. 클라이언트는 최소한 다음 정보를 기준으로 패키지를 검증해야 합니다.
| 검증 항목 | 목적 | 실패 시 처리 |
|---|---|---|
| 파일 크기 | 다운로드가 중간에 잘렸는지 확인 | 재개 또는 재다운로드 |
| SHA-256 해시 | 내용이 배포 원본과 같은지 확인 | 손상 청크 또는 전체 재다운로드 |
| 매니페스트 버전 | 현재 게임 빌드와 호환되는지 확인 | 올바른 DLC 버전 요청 |
| 청크 해시 | 대용량 파일의 부분 손상 위치 확인 | 해당 청크만 재다운로드 |
| 압축 해제 또는 마운트 | 패키지 구조와 내부 인덱스 확인 | 패키지 폐기 후 재다운로드 |
예를 들어 Unity 2022.3 LTS 기반 클라이언트에서 Addressables 또는 AssetBundle을 사용한다면 매니페스트에는 파일 크기, SHA-256, 빌드 ID, DLC 버전을 함께 기록하는 편이 안전합니다. Unreal Engine 5 기반의 Pak 또는 IoStore 파일도 동일하게 배포 매니페스트와 런타임 검증 단계를 분리해 관리할 수 있습니다.
권장 매니페스트와 해시 검증 기준
매니페스트 예시는 다음과 같습니다.
{
"packageId": "dlc-forest-01",
"buildId": "pc-1.4.0-20260829",
"fileName": "dlc-forest-01.pak",
"size": 2147483648,
"chunkSize": 8388608,
"sha256": "replace-with-the-64-hex-character-package-digest",
"chunkHashes": [
"..."
]
}
전체 파일 검증은 다음 조건을 모두 만족해야 통과합니다.
청크 단위 검증을 사용한다면 각 청크의 해시가 매니페스트와 같아야 합니다.
부분 다운로드 파일은 파일 이름에 .part 같은 임시 확장자를 붙이고 검증을 통과한 뒤에만 최종 이름으로 변경해야 합니다. 이렇게 해야 게임 실행 중 미완성 DLC가 정상 설치된 것처럼 보이는 문제를 막을 수 있습니다.
다운로드 재개·재검증·재다운로드 흐름은 어떻게 동작하는가?
일반적인 상태 전이는 다음과 같습니다.
flowchart TD
A[다운로드 시작] --> B[임시 파일 크기 확인]
B --> C{서버가 Range 재개를 지원하는가?}
C -->|예| D[Range 요청으로 이어받기]
C -->|아니오| E[전체 파일 다시 다운로드]
D --> F[파일 크기 검증]
E --> F
F --> G{크기가 일치하는가?}
G -->|아니오| H[임시 파일 폐기 후 재다운로드]
G -->|예| I[SHA-256 또는 청크 해시 검증]
I --> J{해시가 일치하는가?}
J -->|예| K[압축 해제 또는 마운트 검증]
J -->|아니오| L[손상 청크 재다운로드]
L --> I
K --> M{패키지 구조가 유효한가?}
M -->|예| N[최종 파일로 원자적 변경]
M -->|아니오| H
1. 재개 다운로드 테스트
재개 테스트에서는 다운로드가 끊긴 시점의 로컬 파일 크기를 기준으로 서버에 다음과 같은 HTTP Range 요청을 보냅니다.
GET /dlc-forest-01.pak HTTP/1.1
Range: bytes=8388608-
If-Range: "etag-value"
검증할 항목은 다음과 같습니다.
- 중단 전까지 기록된 바이트 수와 재개 시작 오프셋이 일치하는가?
- 서버가
206 Partial Content와 올바른Content-Range를 반환하는가? - 서버가 Range를 지원하지 않을 때 전체 다운로드로 안전하게 전환하는가?
ETag또는 매니페스트의buildId가 바뀌면 오래된 부분 파일을 재사용하지 않는가?- 앱 강제 종료 후 다시 실행해도 다운로드 상태와 임시 파일이 일치하는가?
- 여러 재개 시도에서 파일에 중복 바이트가 추가되지 않는가?
특히 서버가 200 OK를 반환했는데 클라이언트가 이를 부분 응답으로 착각하면 파일 앞부분이 중복되어 최종 해시가 달라질 수 있습니다. 상태 코드와 Content-Range를 함께 확인해야 합니다.
2. 재검증 테스트
재검증은 다운로드 완료 직후뿐 아니라 다음 시점에도 수행해야 합니다.
- 앱 재시작 직후
- 설치 또는 압축 해제 직전
- DLC 마운트 직전
- 패치 적용 직후
- 로컬 캐시에서 DLC를 다시 사용할 때
파일 크기는 맞지만 내부 바이트가 바뀐 상황을 반드시 별도로 재현해야 합니다. 예를 들어 8MiB 청크의 중간 바이트를 수정하면 크기 검증은 통과하지만 SHA-256 검증은 실패해야 합니다.
검증 로직의 최소 예시는 다음과 같습니다. 아래 C# 코드는 독립형 .NET 8 예시이며 Unity나 콘솔 클라이언트에 그대로 적용할 수 있는 플랫폼 공통 구현은 아닙니다. 실제 제품에서는 대용량 파일을 한 번에 메모리에 올리지 않고 스트림으로 읽어야 합니다.
using System;
using System.IO;
using System.Security.Cryptography;
public static bool VerifyPackage(string path, long expectedSize, string expectedSha256)
{
var info = new FileInfo(path);
if (!info.Exists || info.Length != expectedSize)
return false;
using var stream = File.OpenRead(path);
using var sha256 = SHA256.Create();
var actualHash = BitConverter.ToString(sha256.ComputeHash(stream)).Replace("-", string.Empty);
return string.Equals(actualHash, expectedSha256, StringComparison.OrdinalIgnoreCase);
}
검증 결과가 성공하더라도 최종 파일로 바꾸는 작업은 임시 파일과 최종 파일이 같은 파일 시스템에 있는지, 대상 플랫폼에서 교체 작업이 원자적으로 보장되는지 확인한 뒤 처리해야 합니다.
if (VerifyPackage(partPath, manifest.Size, manifest.Sha256))
{
File.Move(partPath, finalPath, overwrite: true);
}
else
{
File.Delete(partPath);
StartFullDownload();
}
다른 프로세스가 파일을 읽을 수 있는 환경이라면 검증이 끝난 뒤 파일명 변경을 수행하고 설치 상태 데이터도 함께 커밋해야 합니다. 파일만 이동하고 데이터베이스나 로컬 설정을 갱신하지 않으면 재실행 시 다시 다운로드하는 문제가 생길 수 있습니다.
3. 손상 청크 재다운로드 테스트
청크 해시를 사용하는 경우에는 전체 파일을 버리지 않고 실패한 청크만 다시 받도록 구현할 수 있습니다. 테스트에서는 다음 입력을 조합합니다.
| 테스트 입력 | 기대 결과 |
|---|---|
| 첫 번째 청크 절단 | 첫 번째 청크부터 재개 |
| 중간 청크의 1바이트 변조 | 해당 청크만 재다운로드 |
| 마지막 청크 크기 부족 | 마지막 청크 재다운로드 |
| 여러 청크 동시 변조 | 변조된 청크만 목록화 후 순차 재다운로드 |
| 청크 해시 목록 누락 | 전체 파일 검증으로 전환하거나 설치 차단 |
| 청크 크기 변경 | 기존 부분 파일 무효화 후 새 빌드로 시작 |
청크 재다운로드는 임의의 쓰기 위치를 사용하므로 파일 스트림의 위치 계산이 정확해야 합니다.
마지막 청크는 전체 청크 크기보다 작을 수 있으므로 서버의 Content-Range와 실제 수신 바이트 수를 모두 확인해야 합니다.
손상 상황을 어떻게 재현할 것인가?
실제 버그를 찾으려면 네트워크 오류만 기다리지 말고 테스트 환경에서 손상 조건을 의도적으로 주입해야 합니다.
파일 손상 시나리오
- 다운로드 중간에 앱을 강제 종료한다.
.part파일을 임의의 길이로 잘라낸다.- 파일 중간의 임의 바이트를 변경한다.
- 마지막 청크만 삭제한다.
- 파일은 유지한 채 매니페스트의 해시를 다른 빌드 값으로 바꾼다.
- 디스크 사용량을 제한해 쓰기 도중
ENOSPC또는 플랫폼별 저장 실패를 발생시킨다.
각 시나리오의 핵심은 클라이언트가 오류를 감지하는 것에서 끝나지 않고 올바른 복구 상태로 이동하는지 확인하는 것입니다.
| 시나리오 | 예상 로그 | 최종 상태 |
|---|---|---|
| 네트워크 단절 | DownloadInterrupted | Resumable |
| Range 오프셋 불일치 | RangeMismatch | RestartRequired |
| 크기 불일치 | SizeMismatch | RedownloadRequired |
| SHA-256 불일치 | HashMismatch | ChunkRepair 또는 RedownloadRequired |
| 빌드 ID 불일치 | ManifestVersionMismatch | CacheInvalidated |
| 디스크 부족 | StorageWriteFailed | PausedForStorage |
| 압축 해제 실패 | PackageMountFailed | RedownloadRequired |
네트워크 오류 시나리오
테스트 프록시나 네트워크 시뮬레이터를 사용해 다음 조건을 반복합니다.
- 응답 중간 연결 종료
- 1초에서 30초 사이의 지연
- 패킷 손실과 재전송
- 첫 요청은
206, 재개 요청은200으로 응답 - 잘못된
Content-Length응답 ETag변경- HTTP 416 Range Not Satisfiable 응답
- HTTP 403 또는 서명 URL 만료
- CDN 엣지마다 서로 다른 빌드 파일 제공
HTTP 416이 발생하면 현재 부분 파일의 오프셋이 서버 파일 크기를 초과했을 가능성이 있습니다. 이때 무한 재시도를 하면 안 되며 부분 파일을 폐기하고 매니페스트를 다시 받아야 합니다.
재시도 정책과 캐시 무효화는 어떻게 검증할까?
재시도 정책은 오류 종류에 따라 달라야 합니다.
| 오류 종류 | 재시도 방식 |
|---|---|
| 일시적 네트워크 오류 | 지수 백오프 후 같은 오프셋에서 재개 |
| HTTP 5xx | 제한된 횟수만 재시도 |
| HTTP 401 또는 403 | 인증 또는 서명 URL 갱신 후 재시도 |
| HTTP 416 | 부분 파일과 오프셋 무효화 |
| 해시 불일치 | CDN 경로 변경 또는 전체 재다운로드 |
| 디스크 부족 | 저장 공간 확보 전 일시 중지 |
| 매니페스트 버전 불일치 | 최신 매니페스트 조회 후 캐시 폐기 |
재시도 횟수는 무제한으로 두지 않는 편이 좋습니다. 예를 들어 동일한 청크에서 세 번 연속 해시가 맞지 않으면 CDN 경로를 바꾸고 그래도 실패하면 사용자에게 재시도 가능한 오류로 표시합니다.
캐시 무효화 테스트에서는 다음 조합을 확인합니다.
- 같은
packageId지만buildId가 변경된 경우 - 같은 빌드 ID지만 SHA-256이 변경된 경우
- DLC 버전이 내려간 경우
- 서버 매니페스트에는 새 파일이 있지만 CDN 캐시에는 이전 파일이 남은 경우
- 다운로드 완료 표시만 있고 실제 파일이 삭제된 경우
완료 플래그는 파일 검증 성공 후에만 기록해야 합니다. 반대로 완료 플래그가 먼저 기록되면 손상 파일이 영구적으로 캐시될 수 있습니다.
자동화 테스트에서 반드시 남겨야 할 로그는 무엇인가?
무결성 버그는 사용자 환경에서만 재현되는 경우가 많으므로 다음 필드를 구조화된 로그로 남기는 것이 좋습니다.
{
"event": "PackageVerificationFailed",
"packageId": "dlc-forest-01",
"buildId": "pc-1.4.0-20260829",
"localSize": 2147475456,
"expectedSize": 2147483648,
"resumeOffset": 8388608,
"chunkIndex": 12,
"expectedHash": "...",
"actualHash": "...",
"httpStatus": 206,
"retryCount": 2,
"storageFreeBytes": 4294967296
}
전체 SHA-256 값을 수집할 때는 개인정보나 인증 토큰을 함께 기록하지 않도록 주의해야 합니다. 로그에는 URL의 서명 쿼리 문자열을 그대로 남기지 않는 것이 안전합니다.
패키지 무결성 테스트 체크리스트
기능 테스트
- 정상 다운로드 후 파일 크기와 SHA-256이 일치한다.
- 다운로드 중 앱을 종료해도 다음 실행에서 이어받는다.
- 서버가 Range를 지원하지 않으면 전체 다운로드로 전환한다.
- Range 응답의
Content-Range가 요청 오프셋과 일치하지 않으면 중단한다. - 크기 불일치 파일은 설치 또는 마운트되지 않는다.
- 내용 변조 파일은 SHA-256 검증에서 실패한다.
- 청크 해시 실패 시 해당 청크가 다시 다운로드된다.
- 매니페스트 버전 변경 시 오래된 캐시가 무효화된다.
- 디스크 부족 상태에서 재시도 루프가 발생하지 않는다.
- 검증 성공 후에만 완료 플래그가 기록된다.
회귀 테스트
- 기존 본편 파일과 DLC 파일의 경로가 충돌하지 않는다.
- 패치 적용 후 이전 버전 DLC가 잘못 마운트되지 않는다.
- 다운로드 일시 중지와 재개를 여러 번 반복해도 파일 크기가 증가하지 않는다.
- 여러 DLC를 동시에 받을 때 각 패키지의 오프셋과 해시가 섞이지 않는다.
- 네트워크 복구 후 백그라운드 작업이 중복 생성되지 않는다.
- 콘솔, PC, 모바일에서 임시 파일과 원자적 변경 방식이 플랫폼 규칙에 맞게 동작한다.
자주 묻는 질문(FAQ)
파일 크기만 확인하면 충분한가?
아닙니다. 같은 크기의 파일도 내부 바이트가 다를 수 있습니다. 최소한 SHA-256 같은 암호학적 해시를 함께 검증해야 합니다.
전체 파일 해시와 청크 해시를 모두 사용해야 하는가?
대용량 DLC라면 두 방식을 함께 사용하는 것이 유리합니다. 청크 해시는 손상 위치를 빠르게 찾고 최종 전체 해시는 설치 가능한 파일이 배포 원본과 완전히 같은지 확인합니다.
해시가 계속 맞지 않으면 부분 파일만 지워야 하는가?
원인은 CDN 캐시, 잘못된 매니페스트, 저장 장치 오류, 서버 파일 불일치일 수 있습니다. 먼저 빌드 ID와 매니페스트를 다시 확인하고 다른 CDN 경로를 시도한 뒤에도 실패하면 부분 파일을 폐기하고 전체 재다운로드해야 합니다.
정리
DLC 다운로드 품질은 다운로드 속도보다 실패 후 복구 정확도로 판단해야 합니다. Range 기반 재개, 파일 크기 검증, SHA-256 또는 청크 해시 검증, 패키지 마운트 검증을 하나의 상태 머신으로 묶고 각 실패 조건에 명확한 복구 경로를 부여해야 합니다.
테스트의 완료 기준은 단순히 파일을 끝까지 받은 것이 아닙니다. 중단된 다운로드를 안전하게 재개하고 손상을 탐지하며 오래된 캐시를 폐기하고 검증을 통과한 패키지만 게임에서 사용할 수 있어야 합니다.



