[주의!] 문서의 이전 버전(에 수정)을 보고 있습니다. 최신 버전으로 이동
이 사용자는 특수 권한을 가지고 있습니다.
[ 펼치기 ㆍ 접기 ]
|
[ 펼치기 ㆍ 접기 ] |
1. 개요[편집]
이젠 방송 역사 편집까지 봇이 하는 혁신. 그 혁신을 개발중인 사용자.
생방은 본지 몇개월 안된 유튜브로만 보던 1~2년차 파스텔. 초기의 시작은 강지와 김블루 우결. 그렇게 강지를 알게된후 칸나를 알게되어 이렇게 파스텔이 되었다 카더라.
해둥이 주글께! - 해둥이 -
주석은 혁명이야!
나무위키의 Aidenk, Shirayuki_Hina, Hebi, Yuuri봇계정 와 동일인. 더시드위키의 사용자:Tenko_Shibuki, 사용자:Shirayuki_Hina와 동일인.
프로필 사진 너무 예쁜데... 예쁘고 귀여우니... 별찜좀...
이나리인 해둥이(?)가 아닌데 맞은 그런것. 근데 바람은 아님(?) 음 그니깐... 쨋든 파스텔.
생방은 본지 몇개월 안된 유튜브로만 보던 1~2년차 파스텔. 초기의 시작은 강지와 김블루 우결. 그렇게 강지를 알게된후 칸나를 알게되어 이렇게 파스텔이 되었다 카더라.
해둥이 주글께! - 해둥이 -
주석은 혁명이야!
나무위키의 Aidenk, Shirayuki_Hina, Hebi, Yuuri봇계정 와 동일인. 더시드위키의 사용자:Tenko_Shibuki, 사용자:Shirayuki_Hina와 동일인.
프로필 사진 너무 예쁜데... 예쁘고 귀여우니... 별찜좀...
이나리인 해둥이(?)가 아닌데 맞은 그런것. 근데 바람은 아님(?) 음 그니깐... 쨋든 파스텔.
연습장 바로가기
수필 바로가기
2. 방송 역사 작성 요령[편집]
간단합니다. 치지직에서 생방을 보면됩니다...
가끔 x 또는 카페의 방송 후기나 기록을 가지고 작성합니다.
가끔 x 또는 카페의 방송 후기나 기록을 가지고 작성합니다.
3. 각종 코드 모음[편집]
깃허브에 올리기도 귀찮기에 여기다가 쓰기. 접근성이 여기가 더 편하다...
3.1. 방송 역사 작성 봇 코드[편집]
Python
100%
생방을 보다보면 졸린 경우가 존재한다. 인간의 3대 욕구인 수면욕을 채우기위해 만드는 것.
코드 예상 기능.
- 방송이 켜져있는지 확인한다.
- 날짜를 받는다.
- API로 태그를 받는다.
- API로 실시간 채팅 기록을 받는다.
- 일단 무조건 첫시작은 저챗이다.
- 게임을 할 경우 태그의 게임이름을 받고 {gamename}을/를 하였다. 로 정한다.
- 카페 탐방이나 노래방일 경우를 대비하여 채팅도 같이 확인하도록한다.
- 그뒤 10분후 3번부터 7번까지 똑같이 반복한다.
- 만약 방송이 끝난다면 반복을 종료한다.
- 결과값을 자동 편집 시킨다.
- 그 결과값을 딥러닝시켜 정확도를 높힌다.
3.2. 암호화 예전 코드[편집]
C#
100%
신규 코드가 이상하면[1] 이걸 쓰기위해 백?업본 이건 오류가 안나는 정상적인 코드.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using System.Runtime.CompilerServices;
[Serializable]
public class PlayerData
{
public string playerName;
public int score;
}
public class SecureManager : MonoBehaviour
{
public static SecureManager Instance { get; private set; }
public bool EnableHardening = true;
public bool EnableAntiDebugChecks = true;
public bool EnableRootDetection = true;
public bool EnableDummyMix = false;
public bool EnableFileNameObfuscation = true;
public bool EnableRotation = true;
private const string Magic = "SJMP";
private const byte Version = 1;
private const int SaltLenDefault = 12;
private const int IvLen = 16;
private const int HmacLen = 32;
private const int KeyMaterialLen = 64;
private const int AesKeyLen = 32;
private const int HmacKeyLen = 32;
private const int Pbkdf2Iterations = 20000;
private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C };
private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE };
private const byte Mask = 0xAA;
private string filePath;
private byte[] sessionNonce;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
sessionNonce = LoadOrCreateSessionNonce();
filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData"));
Warmup();
}
private byte[] LoadOrCreateSessionNonce()
{
const string key = "SecureManager_SessionNonce_v1";
try
{
if (PlayerPrefs.HasKey(key))
{
string b64 = PlayerPrefs.GetString(key);
byte[] stored = Convert.FromBase64String(b64);
if (stored != null && stored.Length > 0) return stored;
}
}
catch { }
byte[] nonce = GenerateRandomBytes(16);
try
{
PlayerPrefs.SetString(key, Convert.ToBase64String(nonce));
PlayerPrefs.Save();
}
catch { }
return nonce;
}
private void Warmup()
{
try { _ = GetSecretBytes(); } catch { }
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixA(byte[] input)
{
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int v = input[i];
v = ((v << 3) | (v >> 5)) & 0xFF;
v ^= (i * 37) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixB(byte[] input, int seed)
{
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int v = input[i] ^ (seed >> (i % 8));
v = (v * 13 + (i * 7)) & 0xFF;
v = (v ^ 0x5A) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixC(byte[] input, byte[] nonce)
{
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int n = nonce[i % nonce.Length];
int v = input[i];
v = ((v + n) ^ (n >> (i % 4))) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
private byte[] GetSecretBytes()
{
byte[] k = new byte[obfPartA.Length + obfPartB.Length];
for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask);
for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask);
if (!EnableHardening || !EnableDummyMix)
{
byte[] simple = new byte[k.Length];
Buffer.BlockCopy(k, 0, simple, 0, k.Length);
Array.Clear(k, 0, k.Length);
return simple;
}
byte[] s1 = DumbMixA(k);
// 시간 의존 제거, 디바이스 기반 결정적 시드
int deviceSeed = Application.identifier.GetHashCode() ^ SystemInfo.deviceUniqueIdentifier.GetHashCode();
byte[] s2 = DumbMixB(s1, deviceSeed);
byte[] s3 = DumbMixC(s2, sessionNonce);
for (int r = 0; r < 2; r++)
{
byte[] t = DumbMixA(s3);
byte[] u = DumbMixB(t, deviceSeed ^ r);
for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]);
Array.Clear(t, 0, t.Length);
Array.Clear(u, 0, u.Length);
}
Array.Clear(s1, 0, s1.Length);
Array.Clear(s2, 0, s2.Length);
byte[] result = new byte[k.Length];
for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]);
Array.Clear(k, 0, k.Length);
Array.Clear(s3, 0, s3.Length);
return result;
}
private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce)
{
byte[] secret = GetSecretBytes();
string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier;
Array.Clear(secret, 0, secret.Length);
using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256))
{
byte[] km = kdf.GetBytes(KeyMaterialLen);
if (EnableHardening && EnableRotation && rotationNonce != null)
{
for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length];
}
byte[] aesKey = new byte[AesKeyLen];
byte[] hmacKey = new byte[HmacKeyLen];
Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen);
Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen);
Array.Clear(km, 0, km.Length);
return (aesKey, hmacKey);
}
}
public void Save<T>(T data)
{
if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return;
try
{
string json = JsonUtility.ToJson(data);
byte[] salt = GenerateRandomBytes(SaltLenDefault);
byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16];
var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
byte[] aesKey = keys.aesKey;
byte[] hmacKey = keys.hmacKey;
byte[] plain = Encoding.UTF8.GetBytes(json);
byte[] iv;
byte[] cipher;
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.GenerateIV();
iv = aes.IV;
using (var enc = aes.CreateEncryptor(aes.Key, iv))
cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
}
byte[] ivCipher = new byte[iv.Length + cipher.Length];
Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
byte[] hmac;
using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(ivCipher);
using (MemoryStream ms = new MemoryStream())
{
ms.Write(Encoding.ASCII.GetBytes(Magic), 0, 4);
ms.WriteByte(Version);
ms.WriteByte((byte)salt.Length);
ms.Write(salt, 0, salt.Length);
ms.WriteByte(EnableRotation ? (byte)rotation.Length : (byte)0);
if (EnableRotation) ms.Write(rotation, 0, rotation.Length);
ms.Write(iv, 0, iv.Length);
byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length);
if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes);
ms.Write(cipherLenBytes, 0, 4);
ms.Write(cipher, 0, cipher.Length);
ms.Write(hmac, 0, hmac.Length);
string outBlob = Convert.ToBase64String(ms.ToArray());
File.WriteAllText(filePath, outBlob);
}
ClearBytes(aesKey);
ClearBytes(hmacKey);
ClearBytes(plain);
ClearBytes(iv);
ClearBytes(cipher);
ClearBytes(ivCipher);
ClearBytes(hmac);
ClearBytes(salt);
ClearBytes(rotation);
}
catch { }
}
public T Load<T>()
{
if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return default;
if (!File.Exists(filePath)) return default;
string blob = File.ReadAllText(filePath);
byte[] total;
try { total = Convert.FromBase64String(blob); } catch { return default; }
try
{
using (MemoryStream ms = new MemoryStream(total))
using (BinaryReader br = new BinaryReader(ms))
{
byte[] magic = br.ReadBytes(4);
if (Encoding.ASCII.GetString(magic) != Magic) return default;
byte ver = br.ReadByte();
if (ver != Version) return default;
int saltLen = br.ReadByte();
if (saltLen <= 0 || saltLen > 64) return default;
byte[] salt = br.ReadBytes(saltLen);
int rotationLen = br.ReadByte();
byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16];
byte[] iv = br.ReadBytes(IvLen);
int cipherLen = br.ReadInt32();
if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen);
if (cipherLen <= 0 || cipherLen > total.Length) return default;
byte[] cipher = br.ReadBytes(cipherLen);
byte[] hmac = br.ReadBytes(HmacLen);
byte[] ivCipher = new byte[iv.Length + cipher.Length];
Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
byte[] aesKey = keys.aesKey;
byte[] hmacKey = keys.hmacKey;
byte[] expected;
using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(ivCipher);
bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac);
if (!ok)
{
ClearBytes(aesKey);
ClearBytes(hmacKey);
ClearBytes(expected);
return default;
}
byte[] plain;
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.IV = iv;
using (var dec = aes.CreateDecryptor(aes.Key, aes.IV))
plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
}
string json = Encoding.UTF8.GetString(plain);
ClearBytes(aesKey);
ClearBytes(hmacKey);
ClearBytes(expected);
ClearBytes(plain);
ClearBytes(iv);
ClearBytes(cipher);
ClearBytes(salt);
ClearBytes(rotation);
return JsonUtility.FromJson<T>(json);
}
}
catch { return default; }
}
private static byte[] GenerateRandomBytes(int len)
{
byte[] b = new byte[len];
using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b);
return b;
}
private static void ClearBytes(byte[] b)
{
if (b == null) return;
Array.Clear(b, 0, b.Length);
}
private string ResolveFileName(string baseName)
{
if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat";
byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier);
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(nameBytes);
string b64 = Convert.ToBase64String(hash);
string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x');
ClearBytes(hash);
return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat";
}
}
private bool IsTamperedOrDebug()
{
if (!EnableHardening) return false;
try
{
if (EnableAntiDebugChecks)
{
if (System.Diagnostics.Debugger.IsAttached) return true;
if (System.Diagnostics.Process.GetCurrentProcess().ProcessName.ToLower().Contains("debug")) return true;
}
}
catch { }
try
{
if (EnableRootDetection)
{
string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" };
foreach (var p in suspectPaths) if (File.Exists(p)) return true;
}
}
catch { }
return false;
}
}3.3. 암호화 신규 코드[편집]
C#
100%
오류 안고친 코드.
.NET이 있어야만 실행된다니...
.NET이 있어야만 실행된다니...
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
[Serializable]
public class PlayerData
{
public string playerName;
public int score;
}
public enum DevLogLevel { Trace = 0, Info = 1, Warning = 2, Error = 3, Critical = 4 }
public struct SecureMetrics
{
public long TotalSaveTimeMs;
public long TotalLoadTimeMs;
public int SaveCount;
public int LoadCount;
public string SessionNonceStatus;
public bool IsTamperingForced;
public bool IsEnvironmentTampered;
}
public class SecureManager : MonoBehaviour
{
public static SecureManager Instance { get; private set; }
[Header("보안 강화 제어")]
public bool EnableHardening = true;
public bool EnableAntiDebugChecks = true;
public bool EnableRootDetection = true;
public bool EnableDummyMix = true;
public bool EnableFileNameObfuscation = true;
public bool EnableRotation = true;
[Header("개발자 진단 및 로깅")]
public bool EnableDeveloperLogging = true;
public DevLogLevel DeveloperLogLevel = DevLogLevel.Info;
[Tooltip("안티-치트 로직 테스트를 위해 강제로 Tampered 상태를 주입합니다.")]
public bool ForceTamperingForTest = false;
[Header("데이터 및 I/O 설정")]
public bool EnableTimeValidation = true;
public long MaxAllowedTimeJumpSeconds = 3600 * 6;
[Tooltip("비동기 I/O 사용 여부 (권장)")]
public bool UseAsyncIO = true;
public byte CurrentVersion = 1;
[Tooltip("어셈블리 해시 검증을 위한 16진수 문자열. 비어 있으면 검증하지 않습니다.")]
public string ExpectedAssemblyHashHex = "";
private const int SaltLenDefault = 16;
private const int IvLen = 16;
private const int HmacLen = 32;
private const int KeyMaterialLen = 64;
private const int AesKeyLen = 32;
private const int HmacKeyLen = 32;
private const int Pbkdf2Iterations = 20000;
private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C };
private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE };
private const byte Mask = 0xAA;
private static readonly byte[] ObfMagicBytes = new byte[] { (byte)('S' ^ 0x5A ^ 0), (byte)('J' ^ 0x5A ^ 1), (byte)('M' ^ 0x5A ^ 2), (byte)('P' ^ 0x5A ^ 3) };
private const byte ObfMagicKey = 0x5A;
private string filePath;
private byte[] sessionNonce;
private bool nonceLoadedSecurely = false;
private Stopwatch perfSave = new Stopwatch();
private Stopwatch perfLoad = new Stopwatch();
private long totalSaveMs = 0;
private long totalLoadMs = 0;
private int saveCount = 0;
private int loadCount = 0;
private string devLogFilePath;
private static class Obf
{
public static string Decode(byte[] cipher, byte key)
{
byte[] b = new byte[cipher.Length];
for (int i = 0; i < b.Length; i++) b[i] = (byte)(cipher[i] ^ key ^ (i & 0xFF));
string s = Encoding.UTF8.GetString(b);
Array.Clear(b, 0, b.Length);
return s;
}
}
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
sessionNonce = LoadOrCreateSessionNonce();
PrintSecurityStatus();
filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData"));
devLogFilePath = Path.Combine(Application.persistentDataPath, "securemanager_devlog.txt");
if (!string.IsNullOrEmpty(ExpectedAssemblyHashHex))
{
try
{
string calc = CalculateAssemblyHashPartial(out bool ok);
if (!ok) DevLog("Assembly hash verification not applicable on this platform.", DevLogLevel.Warning);
else
{
if (!VerifyAssemblyHash(ExpectedAssemblyHashHex)) DevLog($"Assembly hash mismatch.", DevLogLevel.Critical);
else DevLog($"Assembly hash verified OK.", DevLogLevel.Info);
}
}
catch (Exception ex) { DevLog("Assembly verify failed due to exception.", DevLogLevel.Error, ex); }
}
DevLog("최종 데이터 경로: " + filePath, DevLogLevel.Info);
DevLogEnvironmentSnapshot();
}
private void DevLogEnvironmentSnapshot()
{
DevLog($"Platform={Application.platform}, OS={SystemInfo.operatingSystem}, Device={SystemInfo.deviceModel}, CPU={SystemInfo.processorType}, Memory={SystemInfo.systemMemorySize}MB", DevLogLevel.Info);
}
public void PrintSecurityStatus()
{
string status = nonceLoadedSecurely
? "SUCCESS: 세션 논스 안전하게 로드됨 (ProtectedData/Native)."
: "WARNING: 세션 논스가 OS 보호 없이 저장됨 (초기 생성 또는 폴백 사용).";
DevLog($"--- 보안 상태 보고서 ---", DevLogLevel.Info);
DevLog($"[논스 상태] {status}", nonceLoadedSecurely ? DevLogLevel.Info : DevLogLevel.Warning);
DevLog($"[강화 설정] 활성: {EnableHardening}, 디버그 방어: {EnableAntiDebugChecks}, 루팅 감지: {EnableRootDetection}", DevLogLevel.Info);
DevLog($"[변조 강제] 강제 테스트 상태: {ForceTamperingForTest}", ForceTamperingForTest ? DevLogLevel.Warning : DevLogLevel.Info);
DevLog($"----------------------------", DevLogLevel.Info);
}
private byte[] LoadOrCreateSessionNonce()
{
const string key = "SecureManager_SessionNonce_v4";
byte[] newNonce = GenerateRandomBytes(16);
try
{
if (PlayerPrefs.HasKey(key))
{
string b64 = PlayerPrefs.GetString(key);
if (!string.IsNullOrEmpty(b64))
{
try
{
byte[] enc = Convert.FromBase64String(b64);
byte[] dec = TryProtectedUnprotect(enc);
if (dec != null && dec.Length > 0)
{
nonceLoadedSecurely = true;
DevLog("Session Nonce successfully loaded and unprotected. [Recovery: Success]", DevLogLevel.Trace);
return dec;
}
DevLog("ProtectedData Unprotect returned null or failed. [Recovery: Regenerate Nonce]", DevLogLevel.Warning);
throw new CryptographicException("DPAPI/OS protection failed.");
}
catch (FormatException ex) { DevLog("Failed to decode session nonce from Base64. [Recovery: Regenerate Nonce]", DevLogLevel.Warning, ex); }
catch (CryptographicException ex) { DevLog("ProtectedData unprotect failed. [Recovery: Regenerate Nonce]", DevLogLevel.Warning, ex); }
catch (Exception ex) { DevLog("Unexpected error during Session Nonce loading/unprotecting.", DevLogLevel.Error, ex); }
}
}
}
catch (Exception ex) { DevLog("Load session nonce failed (PlayerPrefs read error). [Recovery: Regenerate Nonce]", DevLogLevel.Warning, ex); }
nonceLoadedSecurely = false;
DevLog("Creating new Session Nonce (Fallback/Initial).", DevLogLevel.Info);
try
{
byte[] protectedBytes = TryProtectedProtect(newNonce);
string toStore = protectedBytes != null
? Convert.ToBase64String(protectedBytes)
: Convert.ToBase64String(newNonce);
PlayerPrefs.SetString(key, toStore);
PlayerPrefs.Save();
if (protectedBytes == null) DevLog("New nonce saved without OS protection. [Recovery: Fallback to PlayerPrefs]", DevLogLevel.Warning);
else DevLog("New nonce saved with OS protection.", DevLogLevel.Trace);
}
catch (Exception ex) { DevLog("Save new session nonce failed. [Defense: Use in-memory nonce]", DevLogLevel.Critical, ex); }
return newNonce;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixA(byte[] input)
{
if (input == null) return new byte[0];
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int v = input[i];
v = ((v << 3) | (v >> 5)) & 0xFF;
v ^= (i * 37) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixB(byte[] input, int seed)
{
if (input == null) return new byte[0];
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int v = input[i] ^ (seed >> (i % 8));
v = (v * 13 + (i * 7)) & 0xFF;
v = (v ^ 0x5A) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private byte[] DumbMixC(byte[] input, byte[] nonce)
{
if (input == null) return new byte[0];
if (nonce == null || nonce.Length == 0) return (byte[])input.Clone();
byte[] outb = new byte[input.Length];
for (int i = 0; i < input.Length; i++)
{
int n = nonce[i % nonce.Length];
int v = input[i];
v = ((v + n) ^ (n >> (i % 4))) & 0xFF;
outb[i] = (byte)v;
}
return outb;
}
private byte[] GetSecretBytes()
{
byte[] k = new byte[obfPartA.Length + obfPartB.Length];
for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask);
for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask);
if (!EnableHardening || !EnableDummyMix)
{
byte[] simple = new byte[k.Length];
Buffer.BlockCopy(k, 0, simple, 0, k.Length);
Array.Clear(k, 0, k.Length);
return simple;
}
byte[] s1 = DumbMixA(k);
int deviceSeed = 0;
try { deviceSeed = Application.identifier.GetHashCode() ^ (SystemInfo.deviceUniqueIdentifier?.GetHashCode() ?? 0); } catch { deviceSeed = Application.identifier.GetHashCode(); }
byte[] s2 = DumbMixB(s1, deviceSeed);
byte[] s3 = DumbMixC(s2, sessionNonce);
for (int r = 0; r < 2; r++)
{
byte[] t = DumbMixA(s3);
byte[] u = DumbMixB(t, deviceSeed ^ r);
for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]);
Array.Clear(t, 0, t.Length);
Array.Clear(u, 0, u.Length);
}
Array.Clear(s1, 0, s1.Length);
Array.Clear(s2, 0, s2.Length);
byte[] result = new byte[k.Length];
for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]);
Array.Clear(k, 0, k.Length);
Array.Clear(s3, 0, s3.Length);
return result;
}
private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce)
{
byte[] secret = GetSecretBytes();
string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier;
Array.Clear(secret, 0, secret.Length);
try
{
using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256))
{
byte[] km = kdf.GetBytes(KeyMaterialLen);
if (EnableHardening && EnableRotation && rotationNonce != null)
{
for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length];
}
byte[] aesKey = new byte[AesKeyLen];
byte[] hmacKey = new byte[HmacKeyLen];
Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen);
Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen);
Array.Clear(km, 0, km.Length);
return (aesKey, hmacKey);
}
}
catch (Exception ex)
{
DevLog("Key derivation failed. [Defense: Throw]", DevLogLevel.Critical, ex);
throw new InvalidOperationException("Key derivation failed, cannot proceed with crypto operations.", ex);
}
}
public Task SaveAsync<T>(T data) where T : class
{
if (!UseAsyncIO)
{
string json = JsonUtility.ToJson(data);
SaveInternalFromJson(json);
return Task.CompletedTask;
}
string json = JsonUtility.ToJson(data);
return Task.Run(() => SaveInternalFromJson(json));
}
public async Task<T> LoadAsync<T>() where T : class
{
string json = null;
if (!UseAsyncIO) json = LoadInternalGetJson();
else json = await Task.Run(() => LoadInternalGetJson());
if (json == null) return default;
T obj = null;
try
{
obj = JsonUtility.FromJson<T>(json);
}
catch (ArgumentException ex)
{
DevLog($"JSON deserialization failed for type {typeof(T).Name}. [Defense: Return Default]", DevLogLevel.Error, ex);
return default;
}
catch (Exception ex)
{
DevLog($"JSON deserialization failed for type {typeof(T).Name}. [Defense: Return Default]", DevLogLevel.Error, ex);
return default;
}
return obj;
}
private void SaveInternalFromJson(string json)
{
if (IsTamperedOrDebug(out string reason))
{
DevLog($"Save aborted: Tamper/Debug detected - {reason}. [Defense: Abort Save]", DevLogLevel.Warning);
return;
}
perfSave.Restart();
try
{
byte[] salt = GenerateRandomBytes(SaltLenDefault);
byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16];
var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
byte[] aesKey = keys.aesKey;
byte[] hmacKey = keys.hmacKey;
byte[] plain = Encoding.UTF8.GetBytes(json);
byte[] iv;
byte[] cipher;
try
{
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.GenerateIV();
iv = aes.IV;
using (var enc = aes.CreateEncryptor(aes.Key, iv))
cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
}
}
catch (CryptographicException ex) { DevLog("AES Encryption failed. [Defense: Abort Save]", DevLogLevel.Critical, ex); return; }
catch (Exception ex) { DevLog("AES Encryption failed. [Defense: Abort Save]", DevLogLevel.Critical, ex); return; }
long ticks = DateTime.UtcNow.Ticks;
byte[] ticksBytes = BitConverter.GetBytes(ticks);
byte[] hmacInput = new byte[salt.Length + rotation.Length + iv.Length + cipher.Length + ticksBytes.Length];
int pos = 0;
Buffer.BlockCopy(salt, 0, hmacInput, pos, salt.Length); pos += salt.Length;
Buffer.BlockCopy(rotation, 0, hmacInput, pos, rotation.Length); pos += rotation.Length;
Buffer.BlockCopy(iv, 0, hmacInput, pos, iv.Length); pos += iv.Length;
Buffer.BlockCopy(cipher, 0, hmacInput, pos, cipher.Length); pos += cipher.Length;
Buffer.BlockCopy(ticksBytes, 0, hmacInput, pos, ticksBytes.Length);
byte[] hmac;
using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(hmacInput);
byte[] magicBytes = Encoding.ASCII.GetBytes(Obf.Decode(ObfMagicBytes, ObfMagicKey));
byte[] outBytes;
using (MemoryStream ms = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(ms))
{
bw.Write(magicBytes);
bw.Write(CurrentVersion);
bw.Write((byte)salt.Length);
bw.Write(salt);
bw.Write((byte)(EnableRotation ? rotation.Length : 0));
if (EnableRotation) bw.Write(rotation);
bw.Write(iv);
byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length);
if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes);
bw.Write(cipherLenBytes);
bw.Write(cipher);
bw.Write(hmac);
bw.Write(ticksBytes);
outBytes = ms.ToArray();
}
string bak = filePath + ".bak";
try
{
if (File.Exists(filePath)) File.Copy(filePath, bak, true);
}
catch (Exception ex) { DevLog($"Backup creation failed. [Recovery: Continue with Main Write]", DevLogLevel.Warning, ex); }
try
{
File.WriteAllBytes(filePath, outBytes);
FileInfo fi = new FileInfo(filePath);
DevLog($"Save successful: {filePath} ({fi.Length} bytes)", DevLogLevel.Info);
}
catch (IOException ex) { DevLog($"File write failed to {filePath}. [Defense: Throw]", DevLogLevel.Critical, ex); throw; }
catch (Exception ex) { DevLog($"File write failed to {filePath}. [Defense: Throw]", DevLogLevel.Critical, ex); throw; }
ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(plain); ClearBytes(iv); ClearBytes(cipher);
ClearBytes(hmacInput); ClearBytes(hmac); ClearBytes(salt); ClearBytes(rotation);
perfSave.Stop();
saveCount++;
totalSaveMs += perfSave.ElapsedMilliseconds;
DevLog($"Save completed in {perfSave.ElapsedMilliseconds} ms", DevLogLevel.Info);
}
catch (Exception ex) { DevLog("Save operation failed (Critical Catch).", DevLogLevel.Critical, ex); }
}
private string LoadInternalGetJson()
{
if (IsTamperedOrDebug(out string reason))
{
DevLog($"Load aborted: Tamper/Debug detected - {reason}. [Defense: Abort Load]", DevLogLevel.Warning);
return null;
}
perfLoad.Restart();
string json = TryLoadFile(filePath, false);
if (json == null)
{
string bak = filePath + ".bak";
if (File.Exists(bak))
{
DevLog("Main file load failed. Attempting to load from backup. [Recovery: Try Backup]", DevLogLevel.Warning);
json = TryLoadFile(bak, true);
if (json != null)
{
try
{
File.Copy(bak, filePath, true);
DevLog("Main file restored from backup. [Recovery: Success]", DevLogLevel.Trace);
}
catch (IOException ex)
{
DevLog("Failed to restore main file from backup. [Recovery: Continue with JSON]", DevLogLevel.Warning, ex);
}
}
else
{
DevLog("Backup file also failed to load. [Defense: Return Null]", DevLogLevel.Error);
}
}
else
{
DevLog("No main file or backup found to load. [Defense: Return Null]", DevLogLevel.Info);
}
}
perfLoad.Stop();
if (json != null)
{
loadCount++;
totalLoadMs += perfLoad.ElapsedMilliseconds;
DevLog($"Load successful. Load time: {perfLoad.ElapsedMilliseconds} ms", DevLogLevel.Info);
}
return json;
}
private string TryLoadFile(string path, bool isBackup)
{
if (!File.Exists(path)) return null;
byte[] total = null;
try
{
total = File.ReadAllBytes(path);
}
catch (IOException ex) { DevLog($"File read failed for {path}. [Defense: Reject File]", DevLogLevel.Warning, ex); return null; }
try
{
using (MemoryStream ms = new MemoryStream(total))
using (BinaryReader br = new BinaryReader(ms))
{
byte[] magic = br.ReadBytes(4);
string mag = Encoding.ASCII.GetString(magic);
string expectedMagic = Obf.Decode(ObfMagicBytes, ObfMagicKey);
if (mag != expectedMagic) { DevLog($"Magic mismatch in {path}. [Defense: Reject Load]", DevLogLevel.Warning); return null; }
byte ver = br.ReadByte();
if (ver != CurrentVersion) { DevLog($"Version mismatch in {path}: File Version {ver} != Current Version {CurrentVersion}. [Defense: Reject Load]", DevLogLevel.Warning); return null; }
int saltLen = br.ReadByte();
if (saltLen <= 0 || saltLen > 64) { DevLog($"Bad salt length ({saltLen}) in {path}. [Defense: Reject Load]", DevLogLevel.Warning); return null; }
byte[] salt = br.ReadBytes(saltLen);
int rotationLen = br.ReadByte();
byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16];
byte[] iv = br.ReadBytes(IvLen);
int cipherLen = br.ReadInt32();
if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen);
if (cipherLen <= 0 || cipherLen > total.Length) { DevLog($"Bad cipher length ({cipherLen}) in {path}. [Defense: Reject Load]", DevLogLevel.Warning); return null; }
byte[] cipher = br.ReadBytes(cipherLen);
byte[] hmac = br.ReadBytes(HmacLen);
byte[] ticksBytes = br.ReadBytes(8);
long ticks = BitConverter.ToInt64(ticksBytes, 0);
byte[] hmacInput = new byte[salt.Length + rotation.Length + iv.Length + cipher.Length + ticksBytes.Length];
int pos = 0;
Buffer.BlockCopy(salt, 0, hmacInput, pos, salt.Length); pos += salt.Length;
Buffer.BlockCopy(rotation, 0, hmacInput, pos, rotation.Length); pos += rotation.Length;
Buffer.BlockCopy(iv, 0, hmacInput, pos, iv.Length); pos += iv.Length;
Buffer.BlockCopy(cipher, 0, hmacInput, pos, cipher.Length); pos += cipher.Length;
Buffer.BlockCopy(ticksBytes, 0, hmacInput, pos, ticksBytes.Length);
var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
byte[] aesKey = keys.aesKey;
byte[] hmacKey = keys.hmacKey;
byte[] expectedHmac;
using (var h = new HMACSHA256(hmacKey)) expectedHmac = h.ComputeHash(hmacInput);
if (!CryptographicOperations.FixedTimeEquals(expectedHmac, hmac))
{
DevLog($"HMAC mismatch in {path} (data tamper). [Defense: Reject Load and Clear Keys]", DevLogLevel.Critical);
ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expectedHmac);
return null;
}
if (EnableTimeValidation && ticks > 0)
{
long nowTicks = DateTime.UtcNow.Ticks;
long diffSec = Math.Abs(nowTicks - ticks) / TimeSpan.TicksPerSecond;
if (diffSec > MaxAllowedTimeJumpSeconds)
{
DevLog($"Time jump detected: {diffSec} seconds > Max ({MaxAllowedTimeJumpSeconds}). [Defense: Reject Load]", DevLogLevel.Warning);
ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expectedHmac);
return null;
}
}
byte[] plain;
try
{
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.IV = iv;
using (var dec = aes.CreateDecryptor(aes.Key, aes.IV))
plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
}
}
catch (CryptographicException ex)
{
DevLog($"AES Decryption failed in {path}. [Defense: Reject Load]", DevLogLevel.Warning, ex);
ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expectedHmac);
return null;
}
string json = Encoding.UTF8.GetString(plain);
ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expectedHmac); ClearBytes(plain);
ClearBytes(iv); ClearBytes(cipher); ClearBytes(salt); ClearBytes(rotation);
return json;
}
}
catch (EndOfStreamException ex) { DevLog($"File structure truncated in {path}. [Defense: Reject File]", DevLogLevel.Error, ex); return null; }
catch (Exception ex) { DevLog($"Load processing failed (General Error) for {path}. [Defense: Reject File]", DevLogLevel.Critical, ex); return null; }
}
public SecureMetrics GetDiagnosticMetrics()
{
string reason = "";
bool isTampered = IsTamperedOrDebug(out reason);
return new SecureMetrics
{
TotalSaveTimeMs = totalSaveMs,
TotalLoadTimeMs = totalLoadMs,
SaveCount = saveCount,
LoadCount = loadCount,
SessionNonceStatus = nonceLoadedSecurely ? "SECURELY_LOADED" : "FALLBACK_USED",
IsTamperingForced = ForceTamperingForTest,
IsEnvironmentTampered = isTampered
};
}
private bool IsTamperedOrDebug(out string reason)
{
reason = "";
if (ForceTamperingForTest) { reason = "Forced via Inspector/Dev Tools"; return true; }
if (!EnableHardening) return false;
try
{
if (EnableAntiDebugChecks)
{
if (System.Diagnostics.Debugger.IsAttached) { reason = "Debugger Attached"; return true; }
string proc = System.Diagnostics.Process.GetCurrentProcess().ProcessName.ToLower();
if (proc.Contains("debug") || proc.Contains("devenv") || proc.Contains("mono") || proc.Contains("dnspy")) { reason = $"ProcessName suspicious: {proc}"; return true; }
}
}
catch { }
try
{
if (EnableRootDetection)
{
string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" };
foreach (var p in suspectPaths) if (File.Exists(p)) { reason = $"Root path found: {p}"; return true; }
}
}
catch { }
return false;
}
private bool VerifyAssemblyHash(string expectedHex)
{
try
{
Assembly asm = typeof(SecureManager).Assembly;
string loc = asm.Location;
if (string.IsNullOrEmpty(loc)) return true;
byte[] bytes = File.ReadAllBytes(loc);
using (var sha = SHA256.Create())
{
byte[] h = sha.ComputeHash(bytes);
string hex = BitConverter.ToString(h).Replace("-", "").ToLowerInvariant();
return hex == expectedHex.ToLowerInvariant();
}
}
catch { return true; }
}
private string CalculateAssemblyHashPartial(out bool success)
{
success = false;
try
{
Assembly asm = typeof(SecureManager).Assembly;
string loc = asm.Location;
if (string.IsNullOrEmpty(loc)) return "";
byte[] bytes = File.ReadAllBytes(loc);
using (var sha = SHA256.Create())
{
byte[] h = sha.ComputeHash(bytes);
string hex = BitConverter.ToString(h).Replace("-", "").ToLowerInvariant();
success = true;
return hex;
}
}
catch { return ""; }
}
private void DevLog(string message, DevLogLevel level = DevLogLevel.Info, Exception ex = null, [CallerMemberName] string methodName = "")
{
if (!EnableDeveloperLogging || level < DeveloperLogLevel) return;
string prefix = $"[SM:{level}][{methodName}] ";
string line = prefix + message;
if (ex != null)
{
line += $" | EXCEPTION_TYPE: {ex.GetType().Name} | EXCEPTION_MSG: {ex.Message}";
if (level >= DevLogLevel.Error) line += $"\n--- STACK TRACE ---\n{ex.StackTrace}";
}
try
{
if (level == DevLogLevel.Critical) UnityEngine.Debug.LogError("[CRITICAL] " + line);
else if (level == DevLogLevel.Error) UnityEngine.Debug.LogError(line);
else if (level == DevLogLevel.Warning) UnityEngine.Debug.LogWarning(line);
else UnityEngine.Debug.Log(line);
}
catch { }
try
{
File.AppendAllText(devLogFilePath, DateTime.UtcNow.ToString("o") + " " + line + Environment.NewLine);
}
catch { }
}
private static byte[] GenerateRandomBytes(int len)
{
byte[] b = new byte[len];
using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b);
return b;
}
private static void ClearBytes(byte[] b)
{
if (b == null) return;
Array.Clear(b, 0, b.Length);
}
private string ResolveFileName(string baseName)
{
if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat";
byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier);
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(nameBytes);
string b64 = Convert.ToBase64String(hash);
string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x');
ClearBytes(hash);
return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat";
}
}
private byte[] TryProtectedProtect(byte[] data)
{
try
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
return ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser);
#else
return null;
#endif
}
catch { return null; }
}
private byte[] TryProtectedUnprotect(byte[] data)
{
try
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
return ProtectedData.Unprotect(data, null, DataProtectionScope.CurrentUser);
#else
return null;
#endif
}
catch { return null; }
}
}3.3.1. 기능[편집]
- 싱글톤 인스턴스 관리
- 초기화
- 세션 Nonce 생성
- 파일 경로 생성
- Warmup 호출
- 무작위 바이트 생성
- DumbMixA/B/C
- SecretBytes 생성
- 키 파생 (DeriveKeysRotating)
- AES 키/HMAC 키 분리
- Rotation 적용 (옵션)
- Save<T>
- JSON 직렬화
- 난수 Salt 생성
- Rotation 바이트 생성
- AES 키/HMAC 키 생성
- AES 암호화 수행
- HMAC 생성
- 메모리 블롭 생성
- 파일 저장
- 메모리 정리
- Load<T>
- 파일 존재 확인
- Base64 디코딩
- Magic/Version 체크
- Salt/Rotation/IV/Cipher/HMAC 읽기
- 키 파생
- HMAC 검증
- AES 복호화 수행
- JSON 역직렬화
- 메모리 정리
- 파일명 난독화 및 경로
- ResolveFileName(baseName)
- 최종 경로 반환(filePath)
- 개발자 로깅 (EnableDeveloperLogging)
- 로그 레벨 관리
- DevLog 함수: 콘솔 + 파일 기록
- 초기화 환경 로그
- 파일 경로, 백업, 파일 크기 로그
- Save/Load 시간 측정 및 평균
- 키 파생 옵션 로그
- 보안 실패 상세 로그
- 예외 메시지/스택트레이스 기록
- 로그 필터링
- 변조·디버깅 탐지
- Debugger.IsAttached
- 프로세스 이름 확인
- 루팅 경로 검사
- 탐지 시 Save/Load 거부 및 로그 출력
- 어셈블리 무결성 (옵션)
- ExpectedAssemblyHash 비교
- DevLog 해시 일부 출력
- 시간 위변조 검증
- Save 시 UTC ticks 저장
- Load 시 ticks 비교
- 시간 점프 감지 및 로그 출력
- 플랫폼 보호 (Windows DPAPI)
- TryProtectedProtect / TryProtectedUnprotect
- Windows 환경만 적용
- 성능/안정성 보조
- Save/Load 시간 측정 및 평균
- I/O 예외 시 백업 로드 시도
- 유틸리티 헬퍼
- GenerateRandomBytes(len)
- ClearBytes(byte[])
- Obf.Decode
- CalculateAssemblyHashPartial(out bool)
- 파일 버전 및 마이그레이션 자리
- Version 바이트 관리
- MigrateData 가능
3.4. 예전 코드 실행 코드[편집]
using UnityEngine;
/// <summary>
/// SecureManager의 Save 및 Load 메서드를 시연하는 예시 스크립트입니다.
/// </summary>
public class ExampleUsage : MonoBehaviour
{
private readonly string testPlayerName = "Gemini_Test_User";
private readonly int initialScore = 1000;
private readonly int newScore = 5000;
void Start()
{
// SecureManager가 씬에 존재하는지 확인합니다.
if (SecureManager.Instance == null)
{
Debug.LogError("SecureManager 인스턴스를 찾을 수 없습니다. SecureManager.cs를 GameObject에 추가했는지 확인해주세요.");
return;
}
// 1. 데이터 저장 테스트
SaveTestData();
// 2. 데이터 로드 테스트
LoadTestData();
// 3. 데이터 업데이트 및 재저장
UpdateAndSaveTestData();
// 4. 업데이트된 데이터 로드 확인
LoadTestData();
}
void SaveTestData()
{
Debug.Log("--- 1. 초기 데이터 저장 시도 ---");
PlayerData dataToSave = new PlayerData
{
playerName = testPlayerName,
score = initialScore
};
SecureManager.Instance.Save(dataToSave);
Debug.Log($"초기 데이터 저장 완료: Player={dataToSave.playerName}, Score={dataToSave.score}");
}
void LoadTestData()
{
Debug.Log("--- 2. 데이터 로드 시도 ---");
PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();
if (loadedData != null)
{
Debug.Log($"데이터 로드 성공: Player={loadedData.playerName}, Score={loadedData.score}");
}
else
{
Debug.LogWarning("데이터 로드 실패 (파일 없음, 오류 발생, 혹은 디버거 감지).");
}
}
void UpdateAndSaveTestData()
{
Debug.Log("--- 3. 데이터 업데이트 및 재저장 시도 ---");
PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();
if (loadedData != null)
{
loadedData.score = newScore;
SecureManager.Instance.Save(loadedData);
Debug.Log($"데이터 업데이트 및 저장 완료: New Score={loadedData.score}");
}
}
void OnDestroy()
{
// 애플리케이션 종료 시 디버거 체크가 활성화된 경우 에러 방지
if (SecureManager.Instance != null && SecureManager.Instance.EnableAntiDebugChecks)
{
SecureManager.Instance.EnableAntiDebugChecks = false;
Debug.Log("SecureManager: Anti-Debug Checks가 활성화되어 있습니다.");
}
}
}[1] 메모리 관리 실패, 개인정보 침해 우려, 심각한 에러, 심각한 운영체제 에러 유발 등등


