r197
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
r185
60=== 본 코드 ===
r182
61{{{#!syntax csharp
62using System;
63using System.IO;
64using System.Security.Cryptography;
65using System.Text;
66using UnityEngine;
67using System.Diagnostics;
68using System.Runtime.CompilerServices;
69
70[Serializable]
71public class PlayerData
72{
73 public string playerName;
74 public int score;
75}
76
77public class SecureManager : MonoBehaviour
78{
79 public static SecureManager Instance { get; private set; }
80
81 public bool EnableHardening = true;
82 public bool EnableAntiDebugChecks = true;
83 public bool EnableRootDetection = true;
r197
84 public bool EnableDummyMix = false;
85 public bool EnableFileNameObfuscation = true;
r182
86 public bool EnableRotation = true;
87
88 private const string Magic = "SJMP";
89 private const byte Version = 1;
90 private const int SaltLenDefault = 12;
91 private const int IvLen = 16;
92 private const int HmacLen = 32;
93 private const int KeyMaterialLen = 64;
94 private const int AesKeyLen = 32;
95 private const int HmacKeyLen = 32;
96 private const int Pbkdf2Iterations = 20000;
97 private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C };
98 private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE };
99 private const byte Mask = 0xAA;
100 private string filePath;
101 private byte[] sessionNonce;
102
103 void Awake()
104 {
105 if (Instance != null && Instance != this)
106 {
107 Destroy(gameObject);
108 return;
109 }
110 Instance = this;
111 DontDestroyOnLoad(gameObject);
112
113 sessionNonce = GenerateRandomBytes(16);
114 filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData"));
115 Warmup();
116 }
117
118 private void Warmup()
119 {
120 try { _ = GetSecretBytes(); } catch { }
121 }
122
123 [MethodImpl(MethodImplOptions.NoInlining)]
124 private byte[] DumbMixA(byte[] input)
125 {
126 byte[] outb = new byte[input.Length];
127 for (int i = 0; i < input.Length; i++)
128 {
129 int v = input[i];
130 v = ((v << 3) | (v >> 5)) & 0xFF;
131 v ^= (i * 37) & 0xFF;
132 outb[i] = (byte)v;
133 }
134 return outb;
135 }
136
137 [MethodImpl(MethodImplOptions.NoInlining)]
138 private byte[] DumbMixB(byte[] input, int seed)
139 {
140 byte[] outb = new byte[input.Length];
141 for (int i = 0; i < input.Length; i++)
142 {
143 int v = input[i] ^ (seed >> (i % 8));
144 v = (v * 13 + (i * 7)) & 0xFF;
145 v = (v ^ 0x5A) & 0xFF;
146 outb[i] = (byte)v;
147 }
148 return outb;
149 }
150
151 [MethodImpl(MethodImplOptions.NoInlining)]
152 private byte[] DumbMixC(byte[] input, byte[] nonce)
153 {
154 byte[] outb = new byte[input.Length];
155 for (int i = 0; i < input.Length; i++)
156 {
157 int n = nonce[i % nonce.Length];
158 int v = input[i];
159 v = ((v + n) ^ (n >> (i % 4))) & 0xFF;
160 outb[i] = (byte)v;
161 }
162 return outb;
163 }
164
165 private byte[] GetSecretBytes()
166 {
167 byte[] k = new byte[obfPartA.Length + obfPartB.Length];
168 for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask);
169 for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask);
170
171 if (!EnableHardening || !EnableDummyMix)
172 {
173 byte[] simple = new byte[k.Length];
174 Buffer.BlockCopy(k, 0, simple, 0, k.Length);
175 Array.Clear(k, 0, k.Length);
176 return simple;
177 }
178
179 byte[] s1 = DumbMixA(k);
180 int dynamicSeed = (int)(DateTime.UtcNow.Ticks ^ Application.identifier.GetHashCode() ^ Environment.TickCount);
181 byte[] s2 = DumbMixB(s1, dynamicSeed);
182 byte[] s3 = DumbMixC(s2, sessionNonce);
183 for (int r = 0; r < 2; r++)
184 {
185 byte[] t = DumbMixA(s3);
186 byte[] u = DumbMixB(t, dynamicSeed ^ r);
187 for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]);
188 Array.Clear(t, 0, t.Length);
189 Array.Clear(u, 0, u.Length);
190 }
191 Array.Clear(s1, 0, s1.Length);
192 Array.Clear(s2, 0, s2.Length);
193 byte[] result = new byte[k.Length];
194 for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]);
195 Array.Clear(k, 0, k.Length);
196 Array.Clear(s3, 0, s3.Length);
197 return result;
198 }
199
200 private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce)
201 {
202 byte[] secret = GetSecretBytes();
203 string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier;
204 Array.Clear(secret, 0, secret.Length);
205
206 using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256))
207 {
208 byte[] km = kdf.GetBytes(KeyMaterialLen);
209 if (EnableHardening && EnableRotation && rotationNonce != null)
210 {
211 for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length];
212 }
213 byte[] aesKey = new byte[AesKeyLen];
214 byte[] hmacKey = new byte[HmacKeyLen];
215 Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen);
216 Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen);
217 Array.Clear(km, 0, km.Length);
218 return (aesKey, hmacKey);
219 }
220 }
221
222 public void Save<T>(T data)
223 {
224 if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return;
225 try
226 {
227 string json = JsonUtility.ToJson(data);
228 byte[] salt = GenerateRandomBytes(SaltLenDefault);
229 byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16];
230 var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
231 byte[] aesKey = keys.aesKey;
232 byte[] hmacKey = keys.hmacKey;
233 byte[] plain = Encoding.UTF8.GetBytes(json);
234 byte[] iv;
235 byte[] cipher;
236 using (Aes aes = Aes.Create())
237 {
238 aes.KeySize = 256;
239 aes.Mode = CipherMode.CBC;
240 aes.Padding = PaddingMode.PKCS7;
241 aes.Key = aesKey;
242 aes.GenerateIV();
243 iv = aes.IV;
244 using (var enc = aes.CreateEncryptor(aes.Key, iv))
245 cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
246 }
247 byte[] ivCipher = new byte[iv.Length + cipher.Length];
248 Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
249 Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
250 byte[] hmac;
251 using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(ivCipher);
252 using (MemoryStream ms = new MemoryStream())
253 {
254 ms.Write(Encoding.ASCII.GetBytes(Magic), 0, 4);
255 ms.WriteByte(Version);
256 ms.WriteByte((byte)salt.Length);
257 ms.Write(salt, 0, salt.Length);
258 ms.WriteByte(EnableRotation ? (byte)rotation.Length : (byte)0);
259 if (EnableRotation) ms.Write(rotation, 0, rotation.Length);
260 ms.Write(iv, 0, iv.Length);
261 byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length);
262 if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes);
263 ms.Write(cipherLenBytes, 0, 4);
264 ms.Write(cipher, 0, cipher.Length);
265 ms.Write(hmac, 0, hmac.Length);
266 string outBlob = Convert.ToBase64String(ms.ToArray());
267 File.WriteAllText(filePath, outBlob);
268 }
269 ClearBytes(aesKey);
270 ClearBytes(hmacKey);
271 ClearBytes(plain);
272 ClearBytes(iv);
273 ClearBytes(cipher);
274 ClearBytes(ivCipher);
275 ClearBytes(hmac);
276 ClearBytes(salt);
277 ClearBytes(rotation);
278 }
279 catch { }
280 }
281
282 public T Load<T>()
283 {
284 if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return default;
285 if (!File.Exists(filePath)) return default;
286 string blob = File.ReadAllText(filePath);
287 byte[] total;
288 try { total = Convert.FromBase64String(blob); } catch { return default; }
289 try
290 {
291 using (MemoryStream ms = new MemoryStream(total))
292 using (BinaryReader br = new BinaryReader(ms))
293 {
294 byte[] magic = br.ReadBytes(4);
295 if (Encoding.ASCII.GetString(magic) != Magic) return default;
296 byte ver = br.ReadByte();
297 if (ver != Version) return default;
298 int saltLen = br.ReadByte();
299 if (saltLen <= 0 || saltLen > 64) return default;
300 byte[] salt = br.ReadBytes(saltLen);
301 int rotationLen = br.ReadByte();
302 byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16];
303 byte[] iv = br.ReadBytes(IvLen);
304 int cipherLen = br.ReadInt32();
305 if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen);
306 if (cipherLen <= 0 || cipherLen > total.Length) return default;
307 byte[] cipher = br.ReadBytes(cipherLen);
308 byte[] hmac = br.ReadBytes(HmacLen);
309 byte[] ivCipher = new byte[iv.Length + cipher.Length];
310 Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
311 Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
312 var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
313 byte[] aesKey = keys.aesKey;
314 byte[] hmacKey = keys.hmacKey;
315 byte[] expected;
316 using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(ivCipher);
317 bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac);
318 if (!ok)
319 {
320 ClearBytes(aesKey);
321 ClearBytes(hmacKey);
322 ClearBytes(expected);
323 return default;
324 }
325 byte[] plain;
326 using (Aes aes = Aes.Create())
327 {
328 aes.KeySize = 256;
329 aes.Mode = CipherMode.CBC;
330 aes.Padding = PaddingMode.PKCS7;
331 aes.Key = aesKey;
332 aes.IV = iv;
333 using (var dec = aes.CreateDecryptor(aes.Key, aes.IV))
334 plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
335 }
336 string json = Encoding.UTF8.GetString(plain);
337 ClearBytes(aesKey);
338 ClearBytes(hmacKey);
339 ClearBytes(expected);
340 ClearBytes(plain);
341 ClearBytes(iv);
342 ClearBytes(cipher);
343 ClearBytes(salt);
344 ClearBytes(rotation);
345 return JsonUtility.FromJson<T>(json);
346 }
347 }
348 catch { return default; }
349 }
350
351 private static byte[] GenerateRandomBytes(int len)
352 {
353 byte[] b = new byte[len];
354 using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b);
355 return b;
356 }
357
358 private static void ClearBytes(byte[] b)
359 {
360 if (b == null) return;
361 Array.Clear(b, 0, b.Length);
362 }
363
364 private string ResolveFileName(string baseName)
365 {
366 if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat";
367 byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier);
368 using (var sha = SHA256.Create())
369 {
370 byte[] hash = sha.ComputeHash(nameBytes);
371 string b64 = Convert.ToBase64String(hash);
372 string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x');
373 ClearBytes(hash);
374 return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat";
375 }
376 }
377
378 private bool IsTamperedOrDebug()
379 {
380 if (!EnableHardening) return false;
381 try
382 {
383 if (EnableAntiDebugChecks)
384 {
385 if (Debug.isDebugBuild) return true;
386 if (Debugger.IsAttached) return true;
387 }
388 }
389 catch { }
390 try
391 {
392 if (EnableRootDetection)
393 {
394 string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" };
395 foreach (var p in suspectPaths) if (File.Exists(p)) return true;
396 }
397 }
398 catch { }
399 return false;
400 }
r183
401} }}}
r196
402
r185
403=== 실행 코드 ===
r183
404{{{#!syntax csharp
405using UnityEngine;
406
r185
407/// <summary>
408/// SecureManager의 Save 및 Load 메서드를 시연하는 예시 스크립트입니다.
409/// </summary>
r183
410public class ExampleUsage : MonoBehaviour
411{
412 private readonly string testPlayerName = "Gemini_Test_User";
413 private readonly int initialScore = 1000;
414 private readonly int newScore = 5000;
415
416 void Start()
417 {
r185
418 // SecureManager가 씬에 존재하는지 확인합니다.
r183
419 if (SecureManager.Instance == null)
420 {
421 Debug.LogError("SecureManager 인스턴스를 찾을 수 없습니다. SecureManager.cs를 GameObject에 추가했는지 확인해주세요.");
422 return;
423 }
r185
424
425 // 1. 데이터 저장 테스트
r183
426 SaveTestData();
427
r185
428 // 2. 데이터 로드 테스트
r183
429 LoadTestData();
430
r185
431 // 3. 데이터 업데이트 및 재저장
r183
432 UpdateAndSaveTestData();
r185
433
434 // 4. 업데이트된 데이터 로드 확인
r183
435 LoadTestData();
436 }
437
438 void SaveTestData()
439 {
440 Debug.Log("--- 1. 초기 데이터 저장 시도 ---");
441 PlayerData dataToSave = new PlayerData
442 {
443 playerName = testPlayerName,
444 score = initialScore
445 };
446
447 SecureManager.Instance.Save(dataToSave);
448 Debug.Log($"초기 데이터 저장 완료: Player={dataToSave.playerName}, Score={dataToSave.score}");
449 }
450
451 void LoadTestData()
452 {
453 Debug.Log("--- 2. 데이터 로드 시도 ---");
454 PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();
455
456 if (loadedData != null)
457 {
458 Debug.Log($"데이터 로드 성공: Player={loadedData.playerName}, Score={loadedData.score}");
459 }
460 else
461 {
462 Debug.LogWarning("데이터 로드 실패 (파일 없음, 오류 발생, 혹은 디버거 감지).");
463 }
464 }
465
466 void UpdateAndSaveTestData()
467 {
468 Debug.Log("--- 3. 데이터 업데이트 및 재저장 시도 ---");
469 PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();
470
471 if (loadedData != null)
472 {
473 loadedData.score = newScore;
474 SecureManager.Instance.Save(loadedData);
475 Debug.Log($"데이터 업데이트 및 저장 완료: New Score={loadedData.score}");
476 }
477 }
478
479 void OnDestroy()
480 {
r185
481 // 애플리케이션 종료 시 디버거 체크가 활성화된 경우 에러 방지
r183
482 if (SecureManager.Instance != null && SecureManager.Instance.EnableAntiDebugChecks)
483 {
r187
484 SecureManager.Instance.EnableAntiDebugChecks = false;
r183
485 Debug.Log("SecureManager: Anti-Debug Checks가 활성화되어 있습니다.");
486 }
487 }
r182
488} }}}