사용자:Aidenk(r201 RAW)보기RAWBlame되돌리기비교[include(틀:유우리)] [include(틀:스텔라이브)] [include(틀:Aidenk/틀 모음, Aidenk=0)] {{{#!wiki style="max-width: 500px; padding: 10px 0; border-radius: 20px; background: linear-gradient(to right, #ba9dff, #ba9dff); float: right; text-align: center" ||<-2><tablewidth=100%><table bgcolor=#fff,#1c1d1f><tablebordercolor=#ba9dff,#ba9dff><colcolor=#fff><color=#00d2ff> || ||<-2><nopad>[youtube(EIii4EXsfsU)]|| ||{{{#!folding 기존 사진 접기 [[파일:special gift of kanna.jpg|width=100%]][br][br][[파일:블아임.png|width=100%]][br][br][[파일:규리감.jpg|width=100%]][br][br][[파일:2025/09/27 유니버스 타비/리제 신의상 합방.jpg|width=100%]]}}}[br][[파일:클리셰 공식 단체 사진.jpg|width=100%]]||}}} [목차] [clearfix] == 개요 == 생방은 본지 몇개월 안된 유튜브로만 보던 1~2년차 파스텔. 초기의 시작은 강지와 김블루 우결. 그렇게 강지를 알게된후 칸나를 알게되어 이렇게 파스텔이 되었다 카더라. 해둥이 주글께! - 해둥이 - 주석은 혁명이야! [[스텔라이브]] 실시간 업데이트 중이진 않음 3기생 사진 준비중 ... 직접 곡을 들으며 starr, end 시간대를 찾는 편집으로 힐링하는 사람. 편집도 하고 노래도 듣고 생방도 보고 너무 좋잖아! [[나무위키]]의 [[https://namu.wiki/w/%EC%82%AC%EC%9A%A9%EC%9E%90:Aidenk|Aidenk]], [[https://namu.wiki/w/%EC%82%AC%EC%9A%A9%EC%9E%90:Airi_Kanna|Airi_Kanna]], [[https://namu.wiki/w/%EC%82%AC%EC%9A%A9%EC%9E%90:Hebi|Hebi^^{{{-3 봇계정}}}^^]] 와 동일인. 더시드위키의 [[사용자:Tenko_Shibuki]], [[사용자:Shirayuki_Hina]]와 동일인. 프로필 사진 너무 예쁜데... 예쁘고 귀여우니... 별찜좀... 이나리로 전향한듯 한 해둥이(?)가 아닌데 이나리인데 해둥이. 근데 바람은 아님(?) 음 그니깐... 쨋든 파스텔. [[/연습장|{{{#!wiki style="display: inline; color: transparent; background: text linear-gradient(to right, #f58abb, #fed09f)" '''연습장 바로가기'''}}}]] [[/버츄얼|{{{#!wiki style="display: inline; color: transparent; background: text linear-gradient(to right, #f58abb, #fed09f)" '''수필 바로가기'''}}}]] == 통포(10/31) == 1. 위: [[시라유키 히나]] 13,700개 1. 위: [[텐코 시부키]] 10,480개 1. 위: [[아라하시 타비]] 1,680개 1. 위: [[아카네 리제]] 1,560 1. 위: [[유즈하 리코]] 1,190 1. 위: [[하나코 나나]] 970개 1. 위: [[네네코 마시로]] 690개 1. 위: [[사키하네 후야]] 590개 1. 위: [[아오쿠모 린]] 460개 1. 위: [[아야츠노 유니]] 310개 1. 위: [[강지]] 300개 ==# 방송 역사 작성 요령 #== 간단합니다. 치지직에서 생방을 보면됩니다. 히나, 부키는 거기다가 방송에서 노래랑 피아노를 자주 쳐주고 불러주기에 힐링됩니다. 힐링 방송을 원하시다면 파스텔 또는 이나리, 해둥이가 되세요! 새벽 4시에 키는게 어디있냐고! 아... 자고싶다... 아닛! 휴대폰 키고 자버려서 노래방 못봤다... 가끔 x 또는 카페의 방송 후기나 기록을 가지고 작성합니다. 유니, 타비, 시로, 리제, 린, 나나, 리코는 작성이 쉽습니다. 하지만 부키, 히나는 작성이 힘듭니다. 히나는 "안자는 해둥이 있나요"는 새벽 3시에 끝나서 작성하기 힘듭니다. 하지만 저는 작성하죠 ㅎㅎ 부키는 평균 방송 시간 7시간 거의 새벽까지 방송하기에 히나와 비슷하게 새벽 3시까지는 깨어있어야합니다. == csharp 암호화 코드 == GZipStream을 이용해서 저장 용량을 50% 줄여보기. === 본 코드 === {{{#!syntax csharp 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; [Serializable] public class PlayerData { public string playerName; public int score; } public enum DevLogLevel { Trace = 0, Info = 1, Warning = 2, Error = 3, Critical = 4 } 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 = true; public bool EnableFileNameObfuscation = true; public bool EnableRotation = true; public bool EnableDeveloperLogging = true; public DevLogLevel DeveloperLogLevel = DevLogLevel.Info; public bool EnableTimeValidation = true; public long MaxAllowedTimeJumpSeconds = 3600 * 6; public bool UseAsyncIO = true; public byte CurrentVersion = 1; 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 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(); 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 { string exp = ExpectedAssemblyHashHex.ToLowerInvariant(); string calcLow = calc.ToLowerInvariant(); if (exp.Length >= 8 && calcLow.Length >= 8) DevLog($"Assembly hash compare: Expected={exp.Substring(0, Math.Min(8, exp.Length))}..., Calculated={calcLow.Substring(0, 8)}...", DevLogLevel.Info); if (!VerifyAssemblyHash(ExpectedAssemblyHashHex)) { DevLog($"Assembly hash mismatch: Expected={ExpectedAssemblyHashHex.Substring(0, Math.Min(8, ExpectedAssemblyHashHex.Length))}..., Calculated={calc.Substring(0, Math.Min(8, calc.Length))}...", DevLogLevel.Warning); } } } catch (Exception ex) { DevLog("Assembly verify exception: " + ex.ToString(), DevLogLevel.Warning); } } DevLog("Final data path: " + 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); } private byte[] LoadOrCreateSessionNonce() { const string key = "SecureManager_SessionNonce_v4"; 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) return dec; return enc; } catch { } } } } catch (Exception ex) { DevLog("Load session nonce failed: " + ex.Message, DevLogLevel.Warning); } byte[] nonce = GenerateRandomBytes(16); try { byte[] protectedBytes = TryProtectedProtect(nonce); string toStore = protectedBytes != null ? Convert.ToBase64String(protectedBytes) : Convert.ToBase64String(nonce); PlayerPrefs.SetString(key, toStore); PlayerPrefs.Save(); } catch (Exception ex) { DevLog("Save session nonce failed: " + ex.Message, DevLogLevel.Warning); } return nonce; } [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); 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); DevLog($"DeriveKeysRotating called. EnableDummyMix={EnableDummyMix}, EnableRotation={EnableRotation}", DevLogLevel.Trace); return (aesKey, hmacKey); } } public void Save<T>(T data) where T : class { if (UseAsyncIO) SaveAsync(data).GetAwaiter().GetResult(); else SaveInternal(data); } public T Load<T>() where T : class { if (UseAsyncIO) return LoadAsync<T>().GetAwaiter().GetResult(); return LoadInternal<T>(); } public Task SaveAsync<T>(T data) where T : class { string json = JsonUtility.ToJson(data); return Task.Run(() => SaveInternalFromJson(json)); } public async Task<T> LoadAsync<T>() where T : class { string json = await Task.Run(() => LoadInternalGetJson()); if (json == null) return default; T obj = null; try { obj = JsonUtility.FromJson<T>(json); DevLog("JSON deserialized on main thread.", DevLogLevel.Trace); } catch (Exception ex) { DevLog("JsonUtility.FromJson failed: " + ex.ToString(), DevLogLevel.Error); return default; } return obj; } private void SaveInternal<T>(T data) where T : class { string json = JsonUtility.ToJson(data); SaveInternalFromJson(json); } private void SaveInternalFromJson(string json) { if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection)) { string reason; if (IsTamperedOrDebug(out reason)) { DevLog($"Save aborted: Tamper/Debug detected - {reason}", 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; 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); } 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)); 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); byte[] outBytes = ms.ToArray(); string bak = filePath + ".bak"; try { if (File.Exists(filePath)) { File.Copy(filePath, bak, true); DevLog("Backup created: " + bak, DevLogLevel.Info); } } catch (Exception ex) { DevLog("Backup creation failed: " + ex.Message, DevLogLevel.Warning); } try { File.WriteAllBytes(filePath, outBytes); FileInfo fi = new FileInfo(filePath); DevLog($"Save complete: {filePath} ({fi.Length} bytes)", DevLogLevel.Info); } catch (Exception ex) { DevLog("File write failed: " + ex.ToString(), DevLogLevel.Error); 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 time: {perfSave.ElapsedMilliseconds} ms (avg {totalSaveMs / (double)saveCount:F1} ms over {saveCount} saves)", DevLogLevel.Info); } catch (Exception ex) { DevLog("Save exception: " + ex.ToString(), DevLogLevel.Error); } } private string LoadInternalGetJson() { if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection)) { string reason; if (IsTamperedOrDebug(out reason)) { DevLog($"Load aborted: Tamper/Debug detected - {reason}", DevLogLevel.Warning); return null; } } perfLoad.Restart(); if (!File.Exists(filePath)) { DevLog("No file to load: " + filePath, DevLogLevel.Info); return null; } byte[] total; try { total = File.ReadAllBytes(filePath); } catch (Exception ex) { DevLog("Read failed: " + ex.Message, DevLogLevel.Warning); string bak = filePath + ".bak"; if (File.Exists(bak)) { try { total = File.ReadAllBytes(bak); DevLog("Loaded from backup: " + bak, DevLogLevel.Info); } catch (Exception ex2) { DevLog("Backup read failed: " + ex2.Message, DevLogLevel.Error); return null; } } else 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.", DevLogLevel.Warning); return null; } byte ver = br.ReadByte(); if (ver != CurrentVersion) { DevLog($"Version mismatch: {ver} != {CurrentVersion}", DevLogLevel.Warning); return null; } int saltLen = br.ReadByte(); if (saltLen <= 0 || saltLen > 64) { DevLog("Bad salt length.", 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.", 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[] expected; using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(hmacInput); bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac); if (!ok) { DevLog("HMAC mismatch - possible tamper or key mismatch.", DevLogLevel.Warning); ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expected); return null; } if (EnableTimeValidation && ticks > 0) { long nowTicks = DateTime.UtcNow.Ticks; long diffSec = Math.Abs(nowTicks - ticks) / TimeSpan.TicksPerSecond; DevLog($"Saved timestamp: {new DateTime(ticks).ToString("o")}, Current UTC: {DateTime.UtcNow.ToString("o")}, diffSec={diffSec}", DevLogLevel.Info); if (diffSec > MaxAllowedTimeJumpSeconds) { DevLog($"Time jump detected: {diffSec} seconds -> rejecting load.", DevLogLevel.Warning); ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expected); return null; } } 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); perfLoad.Stop(); loadCount++; totalLoadMs += perfLoad.ElapsedMilliseconds; DevLog($"Load complete: {filePath} ({total.Length} bytes). Load time: {perfLoad.ElapsedMilliseconds} ms (avg {totalLoadMs / (double)loadCount:F1} ms over {loadCount} loads)", DevLogLevel.Info); return json; } } catch (Exception ex) { DevLog("Load exception: " + ex.ToString(), DevLogLevel.Error); string bak = filePath + ".bak"; if (File.Exists(bak)) { DevLog("Attempting to load from backup.", DevLogLevel.Info); try { byte[] bakTotal = File.ReadAllBytes(bak); using (MemoryStream ms = new MemoryStream(bakTotal)) 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("Backup magic mismatch.", DevLogLevel.Warning); return null; } byte ver = br.ReadByte(); if (ver != CurrentVersion) { DevLog("Backup version mismatch.", DevLogLevel.Warning); return null; } int saltLen = br.ReadByte(); if (saltLen <= 0 || saltLen > 64) { DevLog("Backup bad salt length.", 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 > bakTotal.Length) { DevLog("Backup bad cipher length.", DevLogLevel.Warning); return null; } byte[] cipher = br.ReadBytes(cipherLen); byte[] hmac = br.ReadBytes(HmacLen); byte[] ticksBytes = br.ReadBytes(8); 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[] expected; using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(hmacInput); bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac); if (!ok) { ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expected); DevLog("Backup HMAC mismatch.", DevLogLevel.Warning); return null; } 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); DevLog("Backup load successful.", DevLogLevel.Info); ClearBytes(aesKey); ClearBytes(hmacKey); ClearBytes(expected); ClearBytes(plain); try { File.Copy(bak, filePath, true); DevLog("Main file restored from backup.", DevLogLevel.Info); } catch (Exception ex2) { DevLog("Failed to restore main from backup: " + ex2.Message, DevLogLevel.Warning); } return json; } } catch (Exception ex2) { DevLog("Backup load failed: " + ex2.Message, DevLogLevel.Error); return null; } } return null; } } 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(out string reason) { reason = ""; if (!EnableHardening) return false; try { if (EnableAntiDebugChecks) { if (System.Diagnostics.Debugger.IsAttached) { reason = "Debugger Attached"; return true; } try { 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 (Exception ex) { DevLog("Process name check failed: " + ex.Message, DevLogLevel.Trace); } } } catch (Exception ex) { DevLog("AntiDebugChecks failed: " + ex.Message, DevLogLevel.Trace); } 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 (Exception ex) { DevLog("Root detection failed: " + ex.Message, DevLogLevel.Trace); } 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)) { success = false; 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 { success = false; return ""; } } private void DevLog(string message, DevLogLevel level = DevLogLevel.Info) { if (!EnableDeveloperLogging) return; if (level < DeveloperLogLevel) return; string prefix = $"[SM:{level}] "; string line = prefix + message; try { if (level == DevLogLevel.Error || level == DevLogLevel.Critical) Debug.LogError(line); else if (level == DevLogLevel.Warning) Debug.LogWarning(line); else Debug.Log(line); } catch { } try { File.AppendAllText(devLogFilePath, DateTime.UtcNow.ToString("o") + " " + line + Environment.NewLine); } catch { } } 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; } } } }}} === 실행 코드 === {{{#!syntax csharp 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가 활성화되어 있습니다."); } } } }}}