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