r195
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
36Aidenk의 노가다를 보기 -> [[사용자:Aidenk/연습장]]
r143
37
r147
38Aidenk의 버츄얼 수필을 보기 -> [[사용자:Aidenk/버츄얼]]
39
r139
40
r184
41== csharp 암호화 코드 ==
r186
42오류가 많을 예정(ai 작성). 최적화 따윈 없는 코드.
r190
43아무리 생각해도 EnableAntiDebugChecks는 오탐지 가능성이 있다. 블랙 리스트에 추가되도록 할지 생각중...
r194
44GZipStream을 이용해서 저장 용량을 50% 줄여보기.
r184
45
r185
46=== 본 코드 ===
r182
47{{{#!syntax csharp
48using System;
49using System.IO;
50using System.Security.Cryptography;
51using System.Text;
52using UnityEngine;
53using System.Diagnostics;
54using System.Runtime.CompilerServices;
55
56[Serializable]
57public class PlayerData
58{
59 public string playerName;
60 public int score;
61}
62
63public 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
390using UnityEngine;
391
r185
392/// <summary>
393/// SecureManager의 Save 및 Load 메서드를 시연하는 예시 스크립트입니다.
394/// </summary>
r183
395public 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} }}}