Unity Asset Store에서 구매 가능한 유료 치트 방지 플러그인이다.
크게 ObscuredTypes, ObscuredPrefs, ObscuredFile, ObscuredFilePrefs 4가지로 나눠져있다.
하나 씩 예제 코드와 함께 살펴보자.
ObscuredTypes
- Cheat Engine, ArtMoney, GameCIH 등과 같은 모든 메모리 검색기에서 변수를 숨김.
- 모든 기본 타입 및 소수의 유니티 타입들을 커버한다.
- 모든 플랫폼에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)
- ObscuredCheatingDectector를 통한 임의 변조 감지 기능이 있다.
보호 기능이 없는 간단한 코드를 상상해 보자. (lives 변수 참고)
using UnityEngine;
public class ObscuredVars : MonoBehaviour
{
private int lives = 5;
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
if (lives > 0)
{
GUILayout.Label("Lives: " + lives);
if (GUILayout.Button("Kill player", GUILayout.ExpandWidth(false)))
{
lives--;
}
}
else
{
GUILayout.Label("Game over!");
if (GUILayout.Button("Start new game", GUILayout.ExpandWidth(false)))
{
lives = 5;
}
}
GUILayout.EndArea();
}
}
일반적으로 치터들은 변수를 찾을 때까지 검색을 반복한다.
아무런 보호 장치가 없다면, 이렇게 찾은 변수를 쉽게 조작할 수 있다.
ACTk을 사용해 변수를 보호하자.
간단하게 int형을 ObscuredInt형으로 교체하자.
public class ObscuredVars : MonoBehaviour
{
private ObscuredInt lives = 5;
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
if (lives > 0)
{
GUILayout.Label("Lives: " + lives);
if (GUILayout.Button("Kill player", GUILayout.ExpandWidth(false)))
{
lives--;
}
}
else
{
GUILayout.Label("Game over!");
if (GUILayout.Button("Start new game", GUILayout.ExpandWidth(false)))
{
lives = 5;
}
}
GUILayout.EndArea();
}
}
이제 해당 타입을 어느정도 보호했지만,
치터가 가짜 사본을 찾고 수정하는 것을 허용할 수 있다.
ObscuredCheatingDetector를 사용하여 부정 행위를 탐지해보자.
Cheater 감지에 대한 반응을 구현하고 Start함수에서 탐지기를 작동하자.
public class ObscuredVars : MonoBehaviour
{
private ObscuredInt lives = 5;
private bool cheaterDetected = false;
private void Start()
{
ObscuredCheatingDetector.StartDetection(OnCheaterDetected);
}
private void OnCheaterDetected()
{
cheaterDetected = true;
}
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
if (lives > 0)
{
GUILayout.Label("Lives: " + lives);
if (GUILayout.Button("Kill player", GUILayout.ExpandWidth(false)))
{
lives--;
}
}
else
{
GUILayout.Label("Game over!");
if (GUILayout.Button("Start new game", GUILayout.ExpandWidth(false)))
{
lives = 5;
}
}
GUILayout.EndArea();
}
}
이렇게 하면 부정 행위 시도가 감지되며 변수는 안전합니다!
ObscuredPrefs
- 저장된 모든 데이터 암호화.
- 변조 감지.
- 저장된 데이터를 현재 디바이스에 잠금.
- PlayerPrefs에서 자동으로 마이그레이션 됨.
- 추가 유형을 지원.
- 모든 플랫폼에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)
보호 기능이 없는 간단한 코드입니다. (PlayerPrefs 사용법 참고)
using UnityEngine;
public class ObscuredPrefsExample : MonoBehaviour
{
private int lives = 5;
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
OutputLivesWithKillButton();
if (GUILayout.Button("Save lives", GUILayout.ExpandWidth(false)))
{
PlayerPrefs.SetInt("player lives", lives);
}
if (GUILayout.Button("Load lives", GUILayout.ExpandWidth(false)))
{
lives = PlayerPrefs.GetInt("player lives", lives);
}
GUILayout.EndArea();
}
private void OutputLivesWithKillButton(){}
}
이렇게 노출된 PlayerPrefs는 변조 되기 쉽다.
다음은 ACTk를 사용한 ObscuredPrefs를 살펴보자.
사용법은 간단하게 PlayerPrefs를 ObscuredPrefs로 교체하면 된다.
using UnityEngine;
using CodeStage.AntiCheat.Storage;
public class ObscuredPrefsExample : MonoBehaviour
{
private int lives = 5;
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
OutputLivesWithKillButton();
if (GUILayout.Button("Save lives", GUILayout.ExpandWidth(false)))
{
ObscuredPrefs.Set("player lives", lives);
}
if (GUILayout.Button("Load lives", GUILayout.ExpandWidth(false)))
{
lives = ObscuredPrefs.Get("player lives", lives);
}
GUILayout.EndArea();
}
private void OutputLivesWithKillButton(){}
}
추가적으로 부정 행위 감지 콜백을 추가하고 적절한 반응을 구현할 수 있다.
public class ObscuredPrefsExample : MonoBehaviour
{
private int lives = 5;
private bool cheated = false;
private void Start()
{
ObscuredPrefs.NotGenuineDataDetected += OnCheatingDetected;
}
private void OnCheatingDetected()
{
cheated = true;
}
private void OnGUI()
{
GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
OutputLivesWithKillButton();
if (cheated)
{
GUILayout.Label("Cheater!");
}
if (GUILayout.Button("Save lives", GUILayout.ExpandWidth(false)))
{
ObscuredPrefs.Set("player lives", lives);
}
if (GUILayout.Button("Load lives", GUILayout.ExpandWidth(false)))
{
lives = ObscuredPrefs.Get("player lives", lives);
}
GUILayout.EndArea();
}
private void OutputLivesWithKillButton(){}
}
ObscuredFile
- 중요한 데이터를 보호하기 위한 파일 암호화 옵션이 있다.
- 파일 편집 시도를 포착하는 변조 감지 기능이 내장되어 있다.
- 실제 데이터 손상으로 인한 오탐 방지
- 암호화 및 일반 모드 모두에서 사용할 수 있다.
- 추가 구성 없이 이벤트만 수신만으로 가능.
- 다른 장치의 데이터를 감지하거나 완전히 방지하는 장치 잠금 옵션이 있다.
- 사용자 정의 ID 옵션. (이메일, 사용자 이름 등)
- 암호화 및 일반 모드 모두에서 사용할 수 있다.
ObscuredFile 요약
- 선택적으로 데이터를 암호화.
- 선택적으로 데이터를 장치 또는 사용자 지정 ID에 잠금.
- 변조 시도를 감지.
- 백그라운드 스레드에서 사용 가능.
- 플랫폼 제한이 없음.
간단한 사용 예:
public class ObscuredFileExample : MonoBehaviour
{
private void Start()
{
var safeFile = new ObscuredFile();
//write data:
var writeResult = safeFile.WriteAllBytes(Encoding.UTF8.GetBytes("Some protected data"));
if(writeResult.Success)
Debug.Log($"Data saved to {safeFile.FilePath}");
else
Debug.Log($"Someting went wrong while writing data: {writeResult.Error}");
//read data:
var readResult = safeFile.ReadAllBytes();
if (readResult.Success)
{
// process readResult.Data;
}
else if (readResult.CheatingDetected)
{
// punish cheaters
// differentiate using DataFromAnotherDevice and DataIsNotGenuine readResult properties
}
else
{
// something went wrong and Data wasn't read
Debug.LogError($"Something went wrong while reading data: {readResult.Error}");
}
}
}
ObscuredFilePrefs
- ObscuredFile의 파워와 유연성.
- PlayerPrefs의 단순성과 사용 용이성.
- Prefs들을 List로 사용할 수 있음.
간단한 사용 예:
public class ObscuredFileExample : MonoBehaviour
{
private void Start()
{
// first, let's init ObscuredFilePrefs and load existing prefs
ObscuredFilePrefs.Init(true);
// now, let's add cheating trigger listeners
// I'll use same listener for both event in sake of demo simplicity
ObscuredFilePrefs.NotGenuineDataDetected += CheaterDetected;
ObscuredFilePrefs.DataFromAnotherDeviceDetected += CheaterDetected;
// few silly prefs for demo purposes
const string playerHealthKey = "Player health";
const string playerSessionStart = "Player session start";
// this is how you can set prefs using generics
ObscuredFilePrefs.Set(playerHealthKey, 100);
ObscuredFilePrefs.Set(playerSessionStart, DateTime.Now);
// getting prefs is easy as well
var loadedHealth = ObscuredFilePrefs.Get(playerHealthKey, 0);
var loadedSessionStart = ObscuredFilePrefs.Get(playerSessionStart, DateTime.MinValue);
Debug.Log($"{playerHealthKey}: {loadedHealth}");
Debug.Log($"{playerSessionStart}: {loadedSessionStart}");
}
private void CheaterDetected()
{
Debug.Log("Cheater!");
}
}
반복문 사용:
// iterating prefs is really easy as well:
var allPrefs = ObscuredFilePrefs.GetKeys();
foreach (var pref in allPrefs)
{
Debug.Log(pref);
}
SpeedHackDetector
- 스피드핵을 감지한다.
- 오탐을 방지하기 위한 쿨다운 기능이 있다.
- 시간 변경을 준수한다.
- 모든 플랫폼에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)
SpeedHack이란?
먼저 스피드 핵이 어떻게 작동하는지 보자.
씬의 큐브에서 이러한 회전 스크립트를 상상해 보자.
public class SpeedHackDetecting : MonoBehaviour
{
private void Update()
{
transform.Rotate(Vector3.up, 60f * Time.deltaTime);
}
}
이 코드는 치터가 치트엔진을 통해 스피드를 쉽게 바꿀 수 있다.
이러한 치트는 예방이 안되지만 감지 및 대응은 가능하다!
먼저 Hierarchy 메뉴에서 스피드핵 디텍터 오브젝트를 만들자.
이제 인스펙터에서 감지기를 구성하고 이벤트 콜백을 할당하기만 하면 된다.
예를 들어 부정 행위 감지 시 게임을 멈추고 팝업을 띄우는 식의 대응이 가능하다.
코드에서 이 작업을 수행할 수 있다.
public class SpeedHackDetecting : MonoBehaviour
{
private bool cheater = false;
private void Start()
{
SpeedHackDetector.StartDetection(OnSpeedHackDetected);
}
private void OnSpeedHackDetected()
{
cheater = true;
}
}
간단하게 Start 함수에서 StartDetection을 해주는 것만으로 스피드핵을 감지할 수 있게 된다.