[주의!] 문서의 이전 버전(에 수정)을 보고 있습니다. 최신 버전으로 이동
이 사용자는 특수 권한을 가지고 있습니다.
[ 펼치기ㆍ접기 ]
1기생
칸나(졸업) · 유니 · 후야(편입)
2기생
3기생
임직원
콘서트


1. 개요2. 통포(10/31)3. 방송 역사 작성 요령4. 개요5. csharp 암호화 코드
5.1. 본 코드5.2. 실행 코드

1. 개요[편집]

생방은 본지 몇개월 안된 유튜브로만 보던 1~2년차 파스텔. 초기의 시작은 강지와 김블루 우결. 그렇게 강지를 알게된후 칸나를 알게되어 이렇게 파스텔이 되었다 카더라.

2. 통포(10/31)[편집]

  1. 위: 시라유키 히나 12,960개
  2. 위: 텐코 시부키 10,400개
  3. 위: 아라하시 타비 1,680개
  4. 위: 아카네 리제 1,560
  5. 위: 유즈하 리코 1,190
  6. 위: 하나코 나나 970개
  7. 위: 아오쿠모 린 460개
  8. 위: 강지 300개

파스텔이라고 모든 방송을 다 보는것이 아니다. 사실상 7위 아래로는 거의 안본다. 유튜브도 이런거 만들어야한다.

3. 방송 역사 작성 요령[편집]

간단합니다. 치지직에서 생방을 보면됩니다. 히나, 부키는 거기다가 방송에서 노래랑 피아노를 자주 쳐주고 불러주기에 힐링됩니다. 힐링 방송을 원하시다면 파스텔 또는 이나리, 해둥이가 되세요! 새벽 4시에 키는게 어디있냐고! 아... 자고싶다... 아닛! 휴대폰 키고 자버려서 노래방 못봤다...

가끔 x 또는 카페의 방송 후기나 기록을 가지고 작성합니다. 유니, 타비, 시로, 리제, 린, 나나, 리코는 작성이 쉽습니다. 하지만 부키, 히나는 작성이 힘듭니다.
히나는 "안자는 해둥이 있나요"는 새벽 3시에 끝나서 작성하기 힘듭니다. 하지만 저는 작성하죠 ㅎㅎ 부키는 평균 방송 시간 7시간 거의 새벽까지 방송하기에 히나와 비슷하게 새벽 3시까지는 깨어있어야합니다.

Aidenk의 노가다를 보기 -> 사용자:Aidenk/연습장

Aidenk의 버츄얼 수필을 보기 -> 사용자:Aidenk/버츄얼

4. 개요[편집]

해둥이 주글께! - 해둥이 -

주석은 혁명이야!

스텔라이브 실시간 업데이트 중이진 않음
3기생 사진 준비중 ...

직접 곡을 들으며 starr, end 시간대를 찾는 편집으로 힐링하는 사람. 편집도 하고 노래도 듣고 생방도 보고 너무 좋잖아!

나무위키Aidenk, Airi_Kanna, Hebi봇계정 와 동일인. 더시드위키의 사용자:Tenko_Shibuki, 사용자:Shirayuki_Hina와 동일인.
프로필 사진 너무 예쁜데... 예쁘고 귀여우니... 별찜좀...

이나리로 전향한듯 한 해둥이(?)가 아닌데 이나리인데 해둥이. 근데 바람은 아님(?)
음 그니깐... 쨋든 파스텔.

연습장 바로가기


5. csharp 암호화 코드[편집]

오류가 많을 예정(ai 작성). 최적화 따윈 없는 코드.
아무리 생각해도 EnableAntiDebugChecks는 오탐지 가능성이 있다. 블랙 리스트에 추가되도록 할지 생각중...

5.1. 본 코드[편집]

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using System.Diagnostics;
using System.Runtime.CompilerServices;

[Serializable]
public class PlayerData
{
    public string playerName;
    public int score;
}

public class SecureManager : MonoBehaviour
{
    public static SecureManager Instance { get; private set; }

    public bool EnableHardening = true;
    public bool EnableAntiDebugChecks = true;
    public bool EnableRootDetection = true;
    public bool EnableDummyMix = true;
    public bool EnableFileNameObfuscation = false;
    public bool EnableRotation = true;

    private const string Magic = "SJMP";
    private const byte Version = 1;
    private const int SaltLenDefault = 12;
    private const int IvLen = 16;
    private const int HmacLen = 32;
    private const int KeyMaterialLen = 64;
    private const int AesKeyLen = 32;
    private const int HmacKeyLen = 32;
    private const int Pbkdf2Iterations = 20000;
    private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C };
    private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE };
    private const byte Mask = 0xAA;
    private string filePath;
    private byte[] sessionNonce;

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);

        sessionNonce = GenerateRandomBytes(16);
        filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData"));
        Warmup();
    }

    private void Warmup()
    {
        try { _ = GetSecretBytes(); } catch { }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private byte[] DumbMixA(byte[] input)
    {
        byte[] outb = new byte[input.Length];
        for (int i = 0; i < input.Length; i++)
        {
            int v = input[i];
            v = ((v << 3) | (v >> 5)) & 0xFF;
            v ^= (i * 37) & 0xFF;
            outb[i] = (byte)v;
        }
        return outb;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private byte[] DumbMixB(byte[] input, int seed)
    {
        byte[] outb = new byte[input.Length];
        for (int i = 0; i < input.Length; i++)
        {
            int v = input[i] ^ (seed >> (i % 8));
            v = (v * 13 + (i * 7)) & 0xFF;
            v = (v ^ 0x5A) & 0xFF;
            outb[i] = (byte)v;
        }
        return outb;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private byte[] DumbMixC(byte[] input, byte[] nonce)
    {
        byte[] outb = new byte[input.Length];
        for (int i = 0; i < input.Length; i++)
        {
            int n = nonce[i % nonce.Length];
            int v = input[i];
            v = ((v + n) ^ (n >> (i % 4))) & 0xFF;
            outb[i] = (byte)v;
        }
        return outb;
    }

    private byte[] GetSecretBytes()
    {
        byte[] k = new byte[obfPartA.Length + obfPartB.Length];
        for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask);
        for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask);

        if (!EnableHardening || !EnableDummyMix)
        {
            byte[] simple = new byte[k.Length];
            Buffer.BlockCopy(k, 0, simple, 0, k.Length);
            Array.Clear(k, 0, k.Length);
            return simple;
        }

        byte[] s1 = DumbMixA(k);
        int dynamicSeed = (int)(DateTime.UtcNow.Ticks ^ Application.identifier.GetHashCode() ^ Environment.TickCount);
        byte[] s2 = DumbMixB(s1, dynamicSeed);
        byte[] s3 = DumbMixC(s2, sessionNonce);
        for (int r = 0; r < 2; r++)
        {
            byte[] t = DumbMixA(s3);
            byte[] u = DumbMixB(t, dynamicSeed ^ r);
            for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]);
            Array.Clear(t, 0, t.Length);
            Array.Clear(u, 0, u.Length);
        }
        Array.Clear(s1, 0, s1.Length);
        Array.Clear(s2, 0, s2.Length);
        byte[] result = new byte[k.Length];
        for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]);
        Array.Clear(k, 0, k.Length);
        Array.Clear(s3, 0, s3.Length);
        return result;
    }

    private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce)
    {
        byte[] secret = GetSecretBytes();
        string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier;
        Array.Clear(secret, 0, secret.Length);

        using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256))
        {
            byte[] km = kdf.GetBytes(KeyMaterialLen);
            if (EnableHardening && EnableRotation && rotationNonce != null)
            {
                for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length];
            }
            byte[] aesKey = new byte[AesKeyLen];
            byte[] hmacKey = new byte[HmacKeyLen];
            Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen);
            Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen);
            Array.Clear(km, 0, km.Length);
            return (aesKey, hmacKey);
        }
    }

    public void Save<T>(T data)
    {
        if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return;
        try
        {
            string json = JsonUtility.ToJson(data);
            byte[] salt = GenerateRandomBytes(SaltLenDefault);
            byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16];
            var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
            byte[] aesKey = keys.aesKey;
            byte[] hmacKey = keys.hmacKey;
            byte[] plain = Encoding.UTF8.GetBytes(json);
            byte[] iv;
            byte[] cipher;
            using (Aes aes = Aes.Create())
            {
                aes.KeySize = 256;
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                aes.Key = aesKey;
                aes.GenerateIV();
                iv = aes.IV;
                using (var enc = aes.CreateEncryptor(aes.Key, iv))
                    cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
            }
            byte[] ivCipher = new byte[iv.Length + cipher.Length];
            Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
            Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
            byte[] hmac;
            using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(ivCipher);
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(Encoding.ASCII.GetBytes(Magic), 0, 4);
                ms.WriteByte(Version);
                ms.WriteByte((byte)salt.Length);
                ms.Write(salt, 0, salt.Length);
                ms.WriteByte(EnableRotation ? (byte)rotation.Length : (byte)0);
                if (EnableRotation) ms.Write(rotation, 0, rotation.Length);
                ms.Write(iv, 0, iv.Length);
                byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length);
                if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes);
                ms.Write(cipherLenBytes, 0, 4);
                ms.Write(cipher, 0, cipher.Length);
                ms.Write(hmac, 0, hmac.Length);
                string outBlob = Convert.ToBase64String(ms.ToArray());
                File.WriteAllText(filePath, outBlob);
            }
            ClearBytes(aesKey);
            ClearBytes(hmacKey);
            ClearBytes(plain);
            ClearBytes(iv);
            ClearBytes(cipher);
            ClearBytes(ivCipher);
            ClearBytes(hmac);
            ClearBytes(salt);
            ClearBytes(rotation);
        }
        catch { }
    }

    public T Load<T>()
    {
        if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return default;
        if (!File.Exists(filePath)) return default;
        string blob = File.ReadAllText(filePath);
        byte[] total;
        try { total = Convert.FromBase64String(blob); } catch { return default; }
        try
        {
            using (MemoryStream ms = new MemoryStream(total))
            using (BinaryReader br = new BinaryReader(ms))
            {
                byte[] magic = br.ReadBytes(4);
                if (Encoding.ASCII.GetString(magic) != Magic) return default;
                byte ver = br.ReadByte();
                if (ver != Version) return default;
                int saltLen = br.ReadByte();
                if (saltLen <= 0 || saltLen > 64) return default;
                byte[] salt = br.ReadBytes(saltLen);
                int rotationLen = br.ReadByte();
                byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16];
                byte[] iv = br.ReadBytes(IvLen);
                int cipherLen = br.ReadInt32();
                if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen);
                if (cipherLen <= 0 || cipherLen > total.Length) return default;
                byte[] cipher = br.ReadBytes(cipherLen);
                byte[] hmac = br.ReadBytes(HmacLen);
                byte[] ivCipher = new byte[iv.Length + cipher.Length];
                Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length);
                Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length);
                var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null));
                byte[] aesKey = keys.aesKey;
                byte[] hmacKey = keys.hmacKey;
                byte[] expected;
                using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(ivCipher);
                bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac);
                if (!ok)
                {
                    ClearBytes(aesKey);
                    ClearBytes(hmacKey);
                    ClearBytes(expected);
                    return default;
                }
                byte[] plain;
                using (Aes aes = Aes.Create())
                {
                    aes.KeySize = 256;
                    aes.Mode = CipherMode.CBC;
                    aes.Padding = PaddingMode.PKCS7;
                    aes.Key = aesKey;
                    aes.IV = iv;
                    using (var dec = aes.CreateDecryptor(aes.Key, aes.IV))
                        plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
                }
                string json = Encoding.UTF8.GetString(plain);
                ClearBytes(aesKey);
                ClearBytes(hmacKey);
                ClearBytes(expected);
                ClearBytes(plain);
                ClearBytes(iv);
                ClearBytes(cipher);
                ClearBytes(salt);
                ClearBytes(rotation);
                return JsonUtility.FromJson<T>(json);
            }
        }
        catch { return default; }
    }

    private static byte[] GenerateRandomBytes(int len)
    {
        byte[] b = new byte[len];
        using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b);
        return b;
    }

    private static void ClearBytes(byte[] b)
    {
        if (b == null) return;
        Array.Clear(b, 0, b.Length);
    }

    private string ResolveFileName(string baseName)
    {
        if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat";
        byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier);
        using (var sha = SHA256.Create())
        {
            byte[] hash = sha.ComputeHash(nameBytes);
            string b64 = Convert.ToBase64String(hash);
            string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x');
            ClearBytes(hash);
            return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat";
        }
    }

    private bool IsTamperedOrDebug()
    {
        if (!EnableHardening) return false;
        try
        {
            if (EnableAntiDebugChecks)
            {
                if (Debug.isDebugBuild) return true;
                if (Debugger.IsAttached) return true;
            }
        }
        catch { }
        try
        {
            if (EnableRootDetection)
            {
                string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" };
                foreach (var p in suspectPaths) if (File.Exists(p)) return true;
            }
        }
        catch { }
        return false;
    }
}

5.2. 실행 코드[편집]

using UnityEngine;

/// <summary>
/// SecureManager의 Save 및 Load 메서드를 시연하는 예시 스크립트입니다.
/// </summary>
public class ExampleUsage : MonoBehaviour
{
    private readonly string testPlayerName = "Gemini_Test_User";
    private readonly int initialScore = 1000;
    private readonly int newScore = 5000;

    void Start()
    {
        // SecureManager가 씬에 존재하는지 확인합니다.
        if (SecureManager.Instance == null)
        {
            Debug.LogError("SecureManager 인스턴스를 찾을 수 없습니다. SecureManager.cs를 GameObject에 추가했는지 확인해주세요.");
            return;
        }

        // 1. 데이터 저장 테스트
        SaveTestData();

        // 2. 데이터 로드 테스트
        LoadTestData();

        // 3. 데이터 업데이트 및 재저장
        UpdateAndSaveTestData();
        
        // 4. 업데이트된 데이터 로드 확인
        LoadTestData();
    }

    void SaveTestData()
    {
        Debug.Log("--- 1. 초기 데이터 저장 시도 ---");
        PlayerData dataToSave = new PlayerData
        {
            playerName = testPlayerName,
            score = initialScore
        };

        SecureManager.Instance.Save(dataToSave);
        Debug.Log($"초기 데이터 저장 완료: Player={dataToSave.playerName}, Score={dataToSave.score}");
    }

    void LoadTestData()
    {
        Debug.Log("--- 2. 데이터 로드 시도 ---");
        PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();

        if (loadedData != null)
        {
            Debug.Log($"데이터 로드 성공: Player={loadedData.playerName}, Score={loadedData.score}");
        }
        else
        {
            Debug.LogWarning("데이터 로드 실패 (파일 없음, 오류 발생, 혹은 디버거 감지).");
        }
    }

    void UpdateAndSaveTestData()
    {
        Debug.Log("--- 3. 데이터 업데이트 및 재저장 시도 ---");
        PlayerData loadedData = SecureManager.Instance.Load<PlayerData>();

        if (loadedData != null)
        {
            loadedData.score = newScore;
            SecureManager.Instance.Save(loadedData);
            Debug.Log($"데이터 업데이트 및 저장 완료: New Score={loadedData.score}");
        }
    }

    void OnDestroy()
    {
        // 애플리케이션 종료 시 디버거 체크가 활성화된 경우 에러 방지
        if (SecureManager.Instance != null && SecureManager.Instance.EnableAntiDebugChecks)
        {
            SecureManager.Instance.EnableAntiDebugChecks = false; 
            Debug.Log("SecureManager: Anti-Debug Checks가 활성화되어 있습니다.");
        }
    }
}