r159
| 1 | [include(틀:유우리)] |
|---|
r158
| 2 | [include(틀:스텔라이브)] |
|---|
r163
| 3 | [include(틀:Aidenk/틀 모음, Aidenk=0)] |
|---|
r85
| 4 | {{{#!wiki style="max-width: 500px; padding: 10px 0; border-radius: 20px; background: linear-gradient(to right, #ba9dff, #ba9dff); float: right; text-align: center" |
|---|
r155
| 5 | ||<-2><tablewidth=100%><table bgcolor=#fff,#1c1d1f><tablebordercolor=#ba9dff,#ba9dff><colcolor=#fff><color=#00d2ff> || |
|---|
r175
| 6 | ||<-2><nopad>[youtube(EIii4EXsfsU)]|| |
|---|
r172
| 7 | ||{{{#!folding 기존 사진 접기 |
|---|
r176
| 8 | [[파일: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%]]||}}} |
|---|
r56
| 9 | |
|---|
r23
| 10 | [목차] |
|---|
| 11 | [clearfix] |
|---|
r167
| 12 | == 개요 == |
|---|
r168
| 13 | 생방은 본지 몇개월 안된 유튜브로만 보던 1~2년차 파스텔. 초기의 시작은 강지와 김블루 우결. 그렇게 강지를 알게된후 칸나를 알게되어 이렇게 파스텔이 되었다 카더라. |
|---|
r167
| 14 | |
|---|
r191
| 15 | == 통포(10/31) == |
|---|
| 16 | 1. 위: [[시라유키 히나]] 12,960개 |
|---|
| 17 | 1. 위: [[텐코 시부키]] 10,400개 |
|---|
r174
| 18 | 1. 위: [[아라하시 타비]] 1,680개 |
|---|
| 19 | 1. 위: [[아카네 리제]] 1,560 |
|---|
| 20 | 1. 위: [[유즈하 리코]] 1,190 |
|---|
| 21 | 1. 위: [[하나코 나나]] 970개 |
|---|
r176
| 22 | 1. 위: [[네네코 마시로]] 660개 |
|---|
r174
| 23 | 1. 위: [[사키하네 후야]] 590개 |
|---|
| 24 | 1. 위: [[아오쿠모 린]] 460개 |
|---|
| 25 | 1. 위: [[아야츠노 유니]] 310개 |
|---|
| 26 | 1. 위: [[강지]] 300개 |
|---|
r167
| 27 | |
|---|
r170
| 28 | 파스텔이라고 모든 방송을 다 보는것이 아니다. 사실상 7위 아래로는 거의 안본다. 유튜브도 이런거 만들어야한다. |
|---|
r169
| 29 | |
|---|
r135
| 30 | == 방송 역사 작성 요령 == |
|---|
r150
| 31 | 간단합니다. 치지직에서 생방을 보면됩니다. 히나, 부키는 거기다가 방송에서 노래랑 피아노를 자주 쳐주고 불러주기에 힐링됩니다. 힐링 방송을 원하시다면 파스텔 또는 이나리, 해둥이가 되세요! 새벽 4시에 키는게 어디있냐고! 아... 자고싶다... 아닛! 휴대폰 키고 자버려서 노래방 못봤다... |
|---|
r135
| 32 | |
|---|
r143
| 33 | 가끔 x 또는 카페의 방송 후기나 기록을 가지고 작성합니다. 유니, 타비, 시로, 리제, 린, 나나, 리코는 작성이 쉽습니다. 하지만 부키, 히나는 작성이 힘듭니다. |
|---|
| 34 | 히나는 "안자는 해둥이 있나요"는 새벽 3시에 끝나서 작성하기 힘듭니다. 하지만 저는 작성하죠 ㅎㅎ 부키는 평균 방송 시간 7시간 거의 새벽까지 방송하기에 히나와 비슷하게 새벽 3시까지는 깨어있어야합니다. |
|---|
| 35 | |
|---|
r156
| 36 | Aidenk의 노가다를 보기 -> [[사용자:Aidenk/연습장]] |
|---|
r143
| 37 | |
|---|
r147
| 38 | Aidenk의 버츄얼 수필을 보기 -> [[사용자:Aidenk/버츄얼]] |
|---|
| 39 | |
|---|
r139
| 40 | |
|---|
r184
| 41 | == csharp 암호화 코드 == |
|---|
r186
| 42 | 오류가 많을 예정(ai 작성). 최적화 따윈 없는 코드. |
|---|
r190
| 43 | 아무리 생각해도 EnableAntiDebugChecks는 오탐지 가능성이 있다. 블랙 리스트에 추가되도록 할지 생각중... |
|---|
r194
| 44 | GZipStream을 이용해서 저장 용량을 50% 줄여보기. |
|---|
r184
| 45 | |
|---|
r185
| 46 | === 본 코드 === |
|---|
r182
| 47 | {{{#!syntax csharp |
|---|
| 48 | using System; |
|---|
| 49 | using System.IO; |
|---|
| 50 | using System.Security.Cryptography; |
|---|
| 51 | using System.Text; |
|---|
| 52 | using UnityEngine; |
|---|
| 53 | using System.Diagnostics; |
|---|
| 54 | using System.Runtime.CompilerServices; |
|---|
| 55 | |
|---|
| 56 | [Serializable] |
|---|
| 57 | public class PlayerData |
|---|
| 58 | { |
|---|
| 59 | public string playerName; |
|---|
| 60 | public int score; |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | public class SecureManager : MonoBehaviour |
|---|
| 64 | { |
|---|
| 65 | public static SecureManager Instance { get; private set; } |
|---|
| 66 | |
|---|
| 67 | public bool EnableHardening = true; |
|---|
| 68 | public bool EnableAntiDebugChecks = true; |
|---|
| 69 | public bool EnableRootDetection = true; |
|---|
| 70 | public bool EnableDummyMix = true; |
|---|
r192
| 71 | public bool EnableFileNameObfuscation = false; |
|---|
r182
| 72 | public bool EnableRotation = true; |
|---|
| 73 | |
|---|
| 74 | private const string Magic = "SJMP"; |
|---|
| 75 | private const byte Version = 1; |
|---|
| 76 | private const int SaltLenDefault = 12; |
|---|
| 77 | private const int IvLen = 16; |
|---|
| 78 | private const int HmacLen = 32; |
|---|
| 79 | private const int KeyMaterialLen = 64; |
|---|
| 80 | private const int AesKeyLen = 32; |
|---|
| 81 | private const int HmacKeyLen = 32; |
|---|
| 82 | private const int Pbkdf2Iterations = 20000; |
|---|
| 83 | private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C }; |
|---|
| 84 | private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE }; |
|---|
| 85 | private const byte Mask = 0xAA; |
|---|
| 86 | private string filePath; |
|---|
| 87 | private byte[] sessionNonce; |
|---|
| 88 | |
|---|
| 89 | void Awake() |
|---|
| 90 | { |
|---|
| 91 | if (Instance != null && Instance != this) |
|---|
| 92 | { |
|---|
| 93 | Destroy(gameObject); |
|---|
| 94 | return; |
|---|
| 95 | } |
|---|
| 96 | Instance = this; |
|---|
| 97 | DontDestroyOnLoad(gameObject); |
|---|
| 98 | |
|---|
| 99 | sessionNonce = GenerateRandomBytes(16); |
|---|
| 100 | filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData")); |
|---|
| 101 | Warmup(); |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | private void Warmup() |
|---|
| 105 | { |
|---|
| 106 | try { _ = GetSecretBytes(); } catch { } |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | [MethodImpl(MethodImplOptions.NoInlining)] |
|---|
| 110 | private byte[] DumbMixA(byte[] input) |
|---|
| 111 | { |
|---|
| 112 | byte[] outb = new byte[input.Length]; |
|---|
| 113 | for (int i = 0; i < input.Length; i++) |
|---|
| 114 | { |
|---|
| 115 | int v = input[i]; |
|---|
| 116 | v = ((v << 3) | (v >> 5)) & 0xFF; |
|---|
| 117 | v ^= (i * 37) & 0xFF; |
|---|
| 118 | outb[i] = (byte)v; |
|---|
| 119 | } |
|---|
| 120 | return outb; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | [MethodImpl(MethodImplOptions.NoInlining)] |
|---|
| 124 | private byte[] DumbMixB(byte[] input, int seed) |
|---|
| 125 | { |
|---|
| 126 | byte[] outb = new byte[input.Length]; |
|---|
| 127 | for (int i = 0; i < input.Length; i++) |
|---|
| 128 | { |
|---|
| 129 | int v = input[i] ^ (seed >> (i % 8)); |
|---|
| 130 | v = (v * 13 + (i * 7)) & 0xFF; |
|---|
| 131 | v = (v ^ 0x5A) & 0xFF; |
|---|
| 132 | outb[i] = (byte)v; |
|---|
| 133 | } |
|---|
| 134 | return outb; |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | [MethodImpl(MethodImplOptions.NoInlining)] |
|---|
| 138 | private byte[] DumbMixC(byte[] input, byte[] nonce) |
|---|
| 139 | { |
|---|
| 140 | byte[] outb = new byte[input.Length]; |
|---|
| 141 | for (int i = 0; i < input.Length; i++) |
|---|
| 142 | { |
|---|
| 143 | int n = nonce[i % nonce.Length]; |
|---|
| 144 | int v = input[i]; |
|---|
| 145 | v = ((v + n) ^ (n >> (i % 4))) & 0xFF; |
|---|
| 146 | outb[i] = (byte)v; |
|---|
| 147 | } |
|---|
| 148 | return outb; |
|---|
| 149 | } |
|---|
| 150 | |
|---|
| 151 | private byte[] GetSecretBytes() |
|---|
| 152 | { |
|---|
| 153 | byte[] k = new byte[obfPartA.Length + obfPartB.Length]; |
|---|
| 154 | for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask); |
|---|
| 155 | for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask); |
|---|
| 156 | |
|---|
| 157 | if (!EnableHardening || !EnableDummyMix) |
|---|
| 158 | { |
|---|
| 159 | byte[] simple = new byte[k.Length]; |
|---|
| 160 | Buffer.BlockCopy(k, 0, simple, 0, k.Length); |
|---|
| 161 | Array.Clear(k, 0, k.Length); |
|---|
| 162 | return simple; |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | byte[] s1 = DumbMixA(k); |
|---|
| 166 | int dynamicSeed = (int)(DateTime.UtcNow.Ticks ^ Application.identifier.GetHashCode() ^ Environment.TickCount); |
|---|
| 167 | byte[] s2 = DumbMixB(s1, dynamicSeed); |
|---|
| 168 | byte[] s3 = DumbMixC(s2, sessionNonce); |
|---|
| 169 | for (int r = 0; r < 2; r++) |
|---|
| 170 | { |
|---|
| 171 | byte[] t = DumbMixA(s3); |
|---|
| 172 | byte[] u = DumbMixB(t, dynamicSeed ^ r); |
|---|
| 173 | for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]); |
|---|
| 174 | Array.Clear(t, 0, t.Length); |
|---|
| 175 | Array.Clear(u, 0, u.Length); |
|---|
| 176 | } |
|---|
| 177 | Array.Clear(s1, 0, s1.Length); |
|---|
| 178 | Array.Clear(s2, 0, s2.Length); |
|---|
| 179 | byte[] result = new byte[k.Length]; |
|---|
| 180 | for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]); |
|---|
| 181 | Array.Clear(k, 0, k.Length); |
|---|
| 182 | Array.Clear(s3, 0, s3.Length); |
|---|
| 183 | return result; |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce) |
|---|
| 187 | { |
|---|
| 188 | byte[] secret = GetSecretBytes(); |
|---|
| 189 | string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier; |
|---|
| 190 | Array.Clear(secret, 0, secret.Length); |
|---|
| 191 | |
|---|
| 192 | using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256)) |
|---|
| 193 | { |
|---|
| 194 | byte[] km = kdf.GetBytes(KeyMaterialLen); |
|---|
| 195 | if (EnableHardening && EnableRotation && rotationNonce != null) |
|---|
| 196 | { |
|---|
| 197 | for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length]; |
|---|
| 198 | } |
|---|
| 199 | byte[] aesKey = new byte[AesKeyLen]; |
|---|
| 200 | byte[] hmacKey = new byte[HmacKeyLen]; |
|---|
| 201 | Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen); |
|---|
| 202 | Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen); |
|---|
| 203 | Array.Clear(km, 0, km.Length); |
|---|
| 204 | return (aesKey, hmacKey); |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | public void Save<T>(T data) |
|---|
| 209 | { |
|---|
| 210 | if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return; |
|---|
| 211 | try |
|---|
| 212 | { |
|---|
| 213 | string json = JsonUtility.ToJson(data); |
|---|
| 214 | byte[] salt = GenerateRandomBytes(SaltLenDefault); |
|---|
| 215 | byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16]; |
|---|
| 216 | var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null)); |
|---|
| 217 | byte[] aesKey = keys.aesKey; |
|---|
| 218 | byte[] hmacKey = keys.hmacKey; |
|---|
| 219 | byte[] plain = Encoding.UTF8.GetBytes(json); |
|---|
| 220 | byte[] iv; |
|---|
| 221 | byte[] cipher; |
|---|
| 222 | using (Aes aes = Aes.Create()) |
|---|
| 223 | { |
|---|
| 224 | aes.KeySize = 256; |
|---|
| 225 | aes.Mode = CipherMode.CBC; |
|---|
| 226 | aes.Padding = PaddingMode.PKCS7; |
|---|
| 227 | aes.Key = aesKey; |
|---|
| 228 | aes.GenerateIV(); |
|---|
| 229 | iv = aes.IV; |
|---|
| 230 | using (var enc = aes.CreateEncryptor(aes.Key, iv)) |
|---|
| 231 | cipher = enc.TransformFinalBlock(plain, 0, plain.Length); |
|---|
| 232 | } |
|---|
| 233 | byte[] ivCipher = new byte[iv.Length + cipher.Length]; |
|---|
| 234 | Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length); |
|---|
| 235 | Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length); |
|---|
| 236 | byte[] hmac; |
|---|
| 237 | using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(ivCipher); |
|---|
| 238 | using (MemoryStream ms = new MemoryStream()) |
|---|
| 239 | { |
|---|
| 240 | ms.Write(Encoding.ASCII.GetBytes(Magic), 0, 4); |
|---|
| 241 | ms.WriteByte(Version); |
|---|
| 242 | ms.WriteByte((byte)salt.Length); |
|---|
| 243 | ms.Write(salt, 0, salt.Length); |
|---|
| 244 | ms.WriteByte(EnableRotation ? (byte)rotation.Length : (byte)0); |
|---|
| 245 | if (EnableRotation) ms.Write(rotation, 0, rotation.Length); |
|---|
| 246 | ms.Write(iv, 0, iv.Length); |
|---|
| 247 | byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length); |
|---|
| 248 | if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes); |
|---|
| 249 | ms.Write(cipherLenBytes, 0, 4); |
|---|
| 250 | ms.Write(cipher, 0, cipher.Length); |
|---|
| 251 | ms.Write(hmac, 0, hmac.Length); |
|---|
| 252 | string outBlob = Convert.ToBase64String(ms.ToArray()); |
|---|
| 253 | File.WriteAllText(filePath, outBlob); |
|---|
| 254 | } |
|---|
| 255 | ClearBytes(aesKey); |
|---|
| 256 | ClearBytes(hmacKey); |
|---|
| 257 | ClearBytes(plain); |
|---|
| 258 | ClearBytes(iv); |
|---|
| 259 | ClearBytes(cipher); |
|---|
| 260 | ClearBytes(ivCipher); |
|---|
| 261 | ClearBytes(hmac); |
|---|
| 262 | ClearBytes(salt); |
|---|
| 263 | ClearBytes(rotation); |
|---|
| 264 | } |
|---|
| 265 | catch { } |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | public T Load<T>() |
|---|
| 269 | { |
|---|
| 270 | if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return default; |
|---|
| 271 | if (!File.Exists(filePath)) return default; |
|---|
| 272 | string blob = File.ReadAllText(filePath); |
|---|
| 273 | byte[] total; |
|---|
| 274 | try { total = Convert.FromBase64String(blob); } catch { return default; } |
|---|
| 275 | try |
|---|
| 276 | { |
|---|
| 277 | using (MemoryStream ms = new MemoryStream(total)) |
|---|
| 278 | using (BinaryReader br = new BinaryReader(ms)) |
|---|
| 279 | { |
|---|
| 280 | byte[] magic = br.ReadBytes(4); |
|---|
| 281 | if (Encoding.ASCII.GetString(magic) != Magic) return default; |
|---|
| 282 | byte ver = br.ReadByte(); |
|---|
| 283 | if (ver != Version) return default; |
|---|
| 284 | int saltLen = br.ReadByte(); |
|---|
| 285 | if (saltLen <= 0 || saltLen > 64) return default; |
|---|
| 286 | byte[] salt = br.ReadBytes(saltLen); |
|---|
| 287 | int rotationLen = br.ReadByte(); |
|---|
| 288 | byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16]; |
|---|
| 289 | byte[] iv = br.ReadBytes(IvLen); |
|---|
| 290 | int cipherLen = br.ReadInt32(); |
|---|
| 291 | if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen); |
|---|
| 292 | if (cipherLen <= 0 || cipherLen > total.Length) return default; |
|---|
| 293 | byte[] cipher = br.ReadBytes(cipherLen); |
|---|
| 294 | byte[] hmac = br.ReadBytes(HmacLen); |
|---|
| 295 | byte[] ivCipher = new byte[iv.Length + cipher.Length]; |
|---|
| 296 | Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length); |
|---|
| 297 | Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length); |
|---|
| 298 | var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null)); |
|---|
| 299 | byte[] aesKey = keys.aesKey; |
|---|
| 300 | byte[] hmacKey = keys.hmacKey; |
|---|
| 301 | byte[] expected; |
|---|
| 302 | using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(ivCipher); |
|---|
| 303 | bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac); |
|---|
| 304 | if (!ok) |
|---|
| 305 | { |
|---|
| 306 | ClearBytes(aesKey); |
|---|
| 307 | ClearBytes(hmacKey); |
|---|
| 308 | ClearBytes(expected); |
|---|
| 309 | return default; |
|---|
| 310 | } |
|---|
| 311 | byte[] plain; |
|---|
| 312 | using (Aes aes = Aes.Create()) |
|---|
| 313 | { |
|---|
| 314 | aes.KeySize = 256; |
|---|
| 315 | aes.Mode = CipherMode.CBC; |
|---|
| 316 | aes.Padding = PaddingMode.PKCS7; |
|---|
| 317 | aes.Key = aesKey; |
|---|
| 318 | aes.IV = iv; |
|---|
| 319 | using (var dec = aes.CreateDecryptor(aes.Key, aes.IV)) |
|---|
| 320 | plain = dec.TransformFinalBlock(cipher, 0, cipher.Length); |
|---|
| 321 | } |
|---|
| 322 | string json = Encoding.UTF8.GetString(plain); |
|---|
| 323 | ClearBytes(aesKey); |
|---|
| 324 | ClearBytes(hmacKey); |
|---|
| 325 | ClearBytes(expected); |
|---|
| 326 | ClearBytes(plain); |
|---|
| 327 | ClearBytes(iv); |
|---|
| 328 | ClearBytes(cipher); |
|---|
| 329 | ClearBytes(salt); |
|---|
| 330 | ClearBytes(rotation); |
|---|
| 331 | return JsonUtility.FromJson<T>(json); |
|---|
| 332 | } |
|---|
| 333 | } |
|---|
| 334 | catch { return default; } |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | private static byte[] GenerateRandomBytes(int len) |
|---|
| 338 | { |
|---|
| 339 | byte[] b = new byte[len]; |
|---|
| 340 | using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b); |
|---|
| 341 | return b; |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | private static void ClearBytes(byte[] b) |
|---|
| 345 | { |
|---|
| 346 | if (b == null) return; |
|---|
| 347 | Array.Clear(b, 0, b.Length); |
|---|
| 348 | } |
|---|
| 349 | |
|---|
| 350 | private string ResolveFileName(string baseName) |
|---|
| 351 | { |
|---|
| 352 | if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat"; |
|---|
| 353 | byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier); |
|---|
| 354 | using (var sha = SHA256.Create()) |
|---|
| 355 | { |
|---|
| 356 | byte[] hash = sha.ComputeHash(nameBytes); |
|---|
| 357 | string b64 = Convert.ToBase64String(hash); |
|---|
| 358 | string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x'); |
|---|
| 359 | ClearBytes(hash); |
|---|
| 360 | return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat"; |
|---|
| 361 | } |
|---|
| 362 | } |
|---|
| 363 | |
|---|
| 364 | private bool IsTamperedOrDebug() |
|---|
| 365 | { |
|---|
| 366 | if (!EnableHardening) return false; |
|---|
| 367 | try |
|---|
| 368 | { |
|---|
| 369 | if (EnableAntiDebugChecks) |
|---|
| 370 | { |
|---|
| 371 | if (Debug.isDebugBuild) return true; |
|---|
| 372 | if (Debugger.IsAttached) return true; |
|---|
| 373 | } |
|---|
| 374 | } |
|---|
| 375 | catch { } |
|---|
| 376 | try |
|---|
| 377 | { |
|---|
| 378 | if (EnableRootDetection) |
|---|
| 379 | { |
|---|
| 380 | string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" }; |
|---|
| 381 | foreach (var p in suspectPaths) if (File.Exists(p)) return true; |
|---|
| 382 | } |
|---|
| 383 | } |
|---|
| 384 | catch { } |
|---|
| 385 | return false; |
|---|
| 386 | } |
|---|
r183
| 387 | } }}} |
|---|
r185
| 388 | === 실행 코드 === |
|---|
r183
| 389 | {{{#!syntax csharp |
|---|
| 390 | using UnityEngine; |
|---|
| 391 | |
|---|
r185
| 392 | /// <summary> |
|---|
| 393 | /// SecureManager의 Save 및 Load 메서드를 시연하는 예시 스크립트입니다. |
|---|
| 394 | /// </summary> |
|---|
r183
| 395 | public class ExampleUsage : MonoBehaviour |
|---|
| 396 | { |
|---|
| 397 | private readonly string testPlayerName = "Gemini_Test_User"; |
|---|
| 398 | private readonly int initialScore = 1000; |
|---|
| 399 | private readonly int newScore = 5000; |
|---|
| 400 | |
|---|
| 401 | void Start() |
|---|
| 402 | { |
|---|
r185
| 403 | // SecureManager가 씬에 존재하는지 확인합니다. |
|---|
r183
| 404 | if (SecureManager.Instance == null) |
|---|
| 405 | { |
|---|
| 406 | Debug.LogError("SecureManager 인스턴스를 찾을 수 없습니다. SecureManager.cs를 GameObject에 추가했는지 확인해주세요."); |
|---|
| 407 | return; |
|---|
| 408 | } |
|---|
r185
| 409 | |
|---|
| 410 | // 1. 데이터 저장 테스트 |
|---|
r183
| 411 | SaveTestData(); |
|---|
| 412 | |
|---|
r185
| 413 | // 2. 데이터 로드 테스트 |
|---|
r183
| 414 | LoadTestData(); |
|---|
| 415 | |
|---|
r185
| 416 | // 3. 데이터 업데이트 및 재저장 |
|---|
r183
| 417 | UpdateAndSaveTestData(); |
|---|
r185
| 418 | |
|---|
| 419 | // 4. 업데이트된 데이터 로드 확인 |
|---|
r183
| 420 | LoadTestData(); |
|---|
| 421 | } |
|---|
| 422 | |
|---|
| 423 | void SaveTestData() |
|---|
| 424 | { |
|---|
| 425 | Debug.Log("--- 1. 초기 데이터 저장 시도 ---"); |
|---|
| 426 | PlayerData dataToSave = new PlayerData |
|---|
| 427 | { |
|---|
| 428 | playerName = testPlayerName, |
|---|
| 429 | score = initialScore |
|---|
| 430 | }; |
|---|
| 431 | |
|---|
| 432 | SecureManager.Instance.Save(dataToSave); |
|---|
| 433 | Debug.Log($"초기 데이터 저장 완료: Player={dataToSave.playerName}, Score={dataToSave.score}"); |
|---|
| 434 | } |
|---|
| 435 | |
|---|
| 436 | void LoadTestData() |
|---|
| 437 | { |
|---|
| 438 | Debug.Log("--- 2. 데이터 로드 시도 ---"); |
|---|
| 439 | PlayerData loadedData = SecureManager.Instance.Load<PlayerData>(); |
|---|
| 440 | |
|---|
| 441 | if (loadedData != null) |
|---|
| 442 | { |
|---|
| 443 | Debug.Log($"데이터 로드 성공: Player={loadedData.playerName}, Score={loadedData.score}"); |
|---|
| 444 | } |
|---|
| 445 | else |
|---|
| 446 | { |
|---|
| 447 | Debug.LogWarning("데이터 로드 실패 (파일 없음, 오류 발생, 혹은 디버거 감지)."); |
|---|
| 448 | } |
|---|
| 449 | } |
|---|
| 450 | |
|---|
| 451 | void UpdateAndSaveTestData() |
|---|
| 452 | { |
|---|
| 453 | Debug.Log("--- 3. 데이터 업데이트 및 재저장 시도 ---"); |
|---|
| 454 | PlayerData loadedData = SecureManager.Instance.Load<PlayerData>(); |
|---|
| 455 | |
|---|
| 456 | if (loadedData != null) |
|---|
| 457 | { |
|---|
| 458 | loadedData.score = newScore; |
|---|
| 459 | SecureManager.Instance.Save(loadedData); |
|---|
| 460 | Debug.Log($"데이터 업데이트 및 저장 완료: New Score={loadedData.score}"); |
|---|
| 461 | } |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | void OnDestroy() |
|---|
| 465 | { |
|---|
r185
| 466 | // 애플리케이션 종료 시 디버거 체크가 활성화된 경우 에러 방지 |
|---|
r183
| 467 | if (SecureManager.Instance != null && SecureManager.Instance.EnableAntiDebugChecks) |
|---|
| 468 | { |
|---|
r187
| 469 | SecureManager.Instance.EnableAntiDebugChecks = false; |
|---|
r183
| 470 | Debug.Log("SecureManager: Anti-Debug Checks가 활성화되어 있습니다."); |
|---|
| 471 | } |
|---|
| 472 | } |
|---|
r182
| 473 | } }}} |
|---|