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