facebook sdk for unity 6.0 got very minor bug
They detect if u have keytool using following function in FacebookAndroidUtil.cs

private static bool DoesCommandExist(string command)
{
var proc = new Process();
if (Application.platform == RuntimePlatform.WindowsEditor)
{
proc.StartInfo.FileName = "cmd";
proc.StartInfo.Arguments = @"/C" + command;
}
else
{
proc.StartInfo.FileName = "bash";
proc.StartInfo.Arguments = @"-c " + command;
}
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.CreateNoWindow = false; 
proc.Start();

proc.WaitForExit();

if (Application.platform == RuntimePlatform.WindowsEditor)
{
return (proc.ExitCode == 0);
}
else
{
return proc.ExitCode != 127;
}
}

If you open a command prompt and type keytool, it show up all the parameter that you can use for the command keytool.
It will not exit with exitCode 0 but 1 which means partially success as no parameter is provided for keytool. so the check for if you have keytool always failed.

Actually you can turn on shell by change proc.StartInfo.UseShellExecute = false; to true and you can see the parameter hint come out for the command keytool , which means keytool is there. it just the DoesCommanExist have a bug.

The fix is change
return (proc.ExitCode == 0);
to
return (proc.ExitCode == 0||proc.ExitCode == 1);

I know nothing about cmd but this page tell me, exitcode 1 mean partially succeed.
http://msdn.microsoft.com/en-us/library/ms194959(v=vs.100).aspx


출처 - http://forum.unity3d.com/threads/keytool-not-found-facebook-sdk-on-android-native-plugin.239696/




WRITTEN BY
빨강꼬마

,
가장 처음 만들었던 게임을 만들고 난 후에 가장 아쉬운 부분은 보안적인 처리가 취약했다는거.
(취약이라기 보다는 전무...)

이번 프로젝트를 진행하면서 내 자신이 그래도 어느정도는 만족할 만한 보안처리(클라이언트 기준)를 하고싶었고,
그래서 나온 결과물이

- PlayerPrefs 암호화 (사용자의 임의적인 값 변경을 해도 영향을 받지 않도록)
- 변수 암호화 (메모리 주소 스캔이 어렵도록)
- 클라이언트와 서버간 통신중 주고받는 데이터의 암호화

이중 2개는 처리했고 남은 이슈는 3번째 이슈인데 이것도 내일 아마 처리될듯 하다.

첫번째 암호화에 대해서는 이전 포스팅으로 기록을 남겼고,
이번 포스팅에서는 기본 변수를 어떻게 저장해야 메모리 스캔을 어렵도록 할수 있을까에 대한 포스팅이다.


아는 게 너무 없어서 이것저것 서칭해본 결과 아래의 클래스를 프로젝트에 만들어 두었다.

using System;

using System.Collections;

using System.Security.Cryptography;

using System.Text;


public static class SecureConverter

{

    private static int xorCode = (UnityEngine.Random.Range(0, 10000) + UnityEngine.Random.Range(0, 10000) + UnityEngine.Random.Range(0, 10000)).GetHashCode();

        

    public static int SecureInt(int data)

    {

        return  data ^ xorCode;

    }


    public static string SecureString(string data)

    {

        string newText = "";


        for (int i = 0; i < data.Length; i++)

        {

            int charValue = Convert.ToInt32(data[i]); //get the ASCII value of the character

            charValue ^= xorCode; //xor the value


            newText += char.ConvertFromUtf32(charValue); //convert back to string

        }


        return newText;

    }

}


사용은 아래처럼 저장할 변수와 프로퍼티를 관리하는 스크립트에 만들어 두고 프로퍼티로 접근하면 자동으로 암호화된 값이 저장되고, 불러올때는 복호화해서 불러오기 때문에 원래의 데이터 그대로를 사용할 수 있게 해두었다.

private int _abc;
public int abc { get { return SecureConvert.SecureInt(_abc); } set { _abc = SecureConvert.SecureInt(value); } }

어디서든 abc = 300; 과 같은 형태로 저장하면 실제 암호화된 형태로 저장되기 때문에 원시적인 데이터 검색만으로는 실제 메모리 주소를 찾기가 어렵게 된다.


추신. MD5 나 SHA1 같은 것으로 대체하면 더욱 좋을듯. 이는 추후 포스팅~


WRITTEN BY
빨강꼬마

,

아무런 처리 없이 PlayerPrefs를 사용할 경우 값이 그대로 노출되면서 접근이 가능하고, 프로그램 밖에서 값을 수정할 경우 수정된 값 그대로 프로그램에 적용되는 문제점이 있음.


그런고로 1차적으로 어느정도는 암호화해야 하는데..구글링 한 결과 아주 만족스럽지는 않지만 적어도 원하지 않는 값으로 프로그램에 적용되는 것을 막아주도록 작성된 내용을 유니티포럼에서 찾아 포스팅한다..

(출처 - http://forum.unity3d.com/threads/playerprefs-encryption.26437/)


아래 코드의 포인트는 테스트해본 결과 실제 저장되는 PlayerPrefs의 값은 원본 그대로 저장된다. 노출자체는 된다는 얘기지. 다만, 직접 해당 유저가 해당 값을 수정하면 CheckEncryption함수를 통과하지 못해 DEFAULT 값으로 처리되면서, 기존에 저장되어 있던 PlayerPrefs의 해당 키(3개)는 모두 삭제처리된다.


using UnityEngine;

using System.Collections;

using System.Security.Cryptography;

using System.Text;

 

public class EncryptedPlayerPrefs  {

 

    // Encrypted PlayerPrefs

    // Written by Sven Magnus

    // MD5 code by Matthew Wegner (from [url]http://www.unifycommunity.com/wiki/index.php?title=MD5[/url])

    

    

    // Modify this key in this file :

    private static string privateKey="9ETrEsWaFRach3gexaDr";

    

    // Add some values to this array before using EncryptedPlayerPrefs

    public static string[] keys;

    

    

    public static string Md5(string strToEncrypt) {

        UTF8Encoding ue = new UTF8Encoding();

        byte[] bytes = ue.GetBytes(strToEncrypt);

 

        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

        byte[] hashBytes = md5.ComputeHash(bytes);

 

        string hashString = "";

 

        for (int i = 0; i < hashBytes.Length; i++) {

            hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');

        }

 

        return hashString.PadLeft(32, '0');

    }

    

    public static void SaveEncryption(string key, string type, string value) {

        int keyIndex = (int)Mathf.Floor(Random.value * keys.Length);

        string secretKey = keys[keyIndex];

        string check = Md5(type + "_" + privateKey + "_" + secretKey + "_" + value);

        PlayerPrefs.SetString(key + "_encryption_check", check);

        PlayerPrefs.SetInt(key + "_used_key", keyIndex);

    }

    

    public static bool CheckEncryption(string key, string type, string value) {

        int keyIndex = PlayerPrefs.GetInt(key + "_used_key");

        string secretKey = keys[keyIndex];

        string check = Md5(type + "_" + privateKey + "_" + secretKey + "_" + value);

        if(!PlayerPrefs.HasKey(key + "_encryption_check")) return false;

        string storedCheck = PlayerPrefs.GetString(key + "_encryption_check");

        return storedCheck == check;

    }

    

    public static void SetInt(string key, int value) {

        PlayerPrefs.SetInt(key, value);

        SaveEncryption(key, "int", value.ToString());

    }

    

    public static void SetFloat(string key, float value) {

        PlayerPrefs.SetFloat(key, value);

        SaveEncryption(key, "float", Mathf.Floor(value*1000).ToString());

    }

    

    public static void SetString(string key, string value) {

        PlayerPrefs.SetString(key, value);

        SaveEncryption(key, "string", value);

    }

    

    public static int GetInt(string key) {

        return GetInt(key, 0);

    }

    

    public static float GetFloat(string key) {

        return GetFloat(key, 0f);

    }

    

    public static string GetString(string key) {

        return GetString(key, "");

    }

    

    public static int GetInt(string key,int defaultValue) {

        int value = PlayerPrefs.GetInt(key);

        if(!CheckEncryption(key, "int", value.ToString())) return defaultValue;

        return value;

    }

    

    public static float GetFloat(string key, float defaultValue) {

        float value = PlayerPrefs.GetFloat(key);

        if(!CheckEncryption(key, "float", Mathf.Floor(value*1000).ToString())) return defaultValue;

        return value;

    }

    

    public static string GetString(string key, string defaultValue) {

        string value = PlayerPrefs.GetString(key);

        if(!CheckEncryption(key, "string", value)) return defaultValue;

        return value;

    }

    

    public static bool HasKey(string key) {

        return PlayerPrefs.HasKey(key);

    }

    

    public static void DeleteKey(string key) {

        PlayerPrefs.DeleteKey(key);

        PlayerPrefs.DeleteKey(key + "_encryption_check");

        PlayerPrefs.DeleteKey(key + "_used_key");

    }

    

}


사용법은 PlayerPrefs의 사용법과 동일하다. (아래의 암호화를 위한 키를 추가한 후 사용하면 된다.) 

  EncryptedPlayerPrefs.keys=new string[5];


        EncryptedPlayerPrefs.keys[0]="23Wrudre";

        EncryptedPlayerPrefs.keys[1]="SP9DupHa";

        EncryptedPlayerPrefs.keys[2]="frA5rAS3";

        EncryptedPlayerPrefs.keys[3]="tHat2epr";

        EncryptedPlayerPrefs.keys[4]="jaw3eDAs";

사용시 PlayerPrefs 클래스 대신 EncryptedPlayerPrefs을 사용하면 된다. 키값 전부 지워주는 것은 기존대로 PlayerPrefs꺼 가져다가 쓰면 됨. 추후 빌드시 난독화 과정을 거치면 어느정도 보안처리를 했다고 볼수 있겠지. 


---- 10월 27일 추가.


위의 클래스를 사용하면 값이 그대로 보이기때문에, 이러한 문제를 해결하기위해서는 PlayerPrefs를 약간 가공해서 쓰는것도 한가지 방법이다.

특정 시크릿키를 사용해 Int일 경우 String으로 변환해서 변환된 문자열을 암호화하여 PlayerPrefs.SetString으로 저장하고 불러올때는 복호화 후 Convert 등을 이용해 다시 int 로 바꾸어 호출해 사용하는 방법이 좋아보인다.


이와 관련하여 암/복호화를 특정키를 사용하여 쉽게 할 수 있도록 하는 클래스는 검색하면 어느정도 나오니 그것을 이용해 구현하는 것이 적합.


---- 2015.06.17

http://ikpil.com/1342


WRITTEN BY
빨강꼬마

,

약 35개 의 객체에 itween을 적용해서 이동시켰을때 프로파일러상에서 GC.collect의 메모리 할당량이 140kb 정도로 부하가 발생함..

해외 사이트를 검색해보니 itween의 경우 부하가 좀 발생한다고 함..

테스트로 NGUI에서 제공하는 tweenPosition 기능으로 변경해서 적용한 결과, 5~60kb 사이로 반 이상 줄어들었음.

타겟 디바이스가 모바일인 경우 이정도 부하면 민감하므로 적용시 참고.


WRITTEN BY
빨강꼬마

,


ABS(A.x - B.x)  <= (A.Scale.x - B.Scale.x) / 2 

'Unity > 스크립트' 카테고리의 다른 글

에셋 Android Native Plugins에 대해  (0) 2015.03.02
String -> enum 변환  (0) 2015.02.17
기본 변수 저장시 암호화  (0) 2014.10.16
Playerprefs 암호화.  (0) 2014.10.14
2D상에서의 각도를 기반으로 회전값 구하기  (4) 2014.06.11

WRITTEN BY
빨강꼬마

,

타겟을 향해 바라봐야한다던가.. 다리같이 뭔가 타겟과 이어주어야 할때 


void Start()

{

// z축 +180은 이미지 방향에 따라 수정하여 적용.

// target1은 자신의 객체가 아닌 비교할 해당 객체.

transform.eulerAngles = new Vector3(0, 0, -getAngle(transform.position.x, transform.position.y, target1.position.x, target1.position.y) + 180.0f);

}


private float getAngle(float x1, float y1, float x2, float y2)

{

        float dx = x2 - x1;

        float dy = y2 - y1;

        

        float rad = Mathf.Atan2(dx, dy);

        float degree = rad * Mathf.Rad2Deg;


        return degree;



----


WRITTEN BY
빨강꼬마

,

유니티 문서를 보면 언급되는 Managed Memory란 단서에서 흔히 추측하기로 유니티가 알아서 모든 메모리를 잘 관리하고 있을 것 처럼 생각되지만 사실은 그렇지 않다는 것이 함정.

사실은 유니티 시스템은 메모리를 어떻게 처리할 것인가에 대한 단서를 당신이 만드는 코드내에서 제공해주기를 원한다. 따라서 잘못된 코드는 줄줄 새는 메모리로 당신의 앱을 디바이스에서 뻗어 버리게 만들것이다. 요즘은 모바일 디바이스에서 조차 64bit 시스템(iPhone 5s)가 올라가고 기본 장착 메모리가 2Gb 이상이 되는 등 모바일 앱으로서는 무한한 자원이 있는것 같지만 사실상 아직도 지구상의 대부분의 유저는 허접한 디바이스로 연명하......


링크 - http://la-stranger.blogspot.kr/2013/09/blog-post_25.html

'Unity > 메모리관리' 카테고리의 다른 글

GC 로깅 (메모리 단편화 추정될때)  (0) 2018.05.15
itween은 되도록 자제...  (0) 2014.08.12
유니티 메모리 관리 4 (마지막)  (2) 2013.10.16
유니티 메모리 관리 3  (0) 2013.10.16
유니티 메모리 관리 2  (0) 2013.10.16

WRITTEN BY
빨강꼬마

,

유니티 <-> MS-SQL 연동.

Unity 2014. 2. 12. 11:05

유니티안 - http://cafe.naver.com/xgdn/10336


WRITTEN BY
빨강꼬마

,

결제모듈 붙이는 작업때문에 그간 고생한걸 생각하면..-,.-... 3분간 묵념을....


음음.. 각설하고 검색을 해봐도 한글로 참고할만한 글이 없어서 정리할겸 기록함..





포인트는 데이터의 흐름을 파악하는거다.

결제를 요청할때 어떤 함수를 호출해야 하고.. 결제가 완료되면 어떤 함수가 호출되고 이런것들


사실 Prime31가져다 쓰기 전에 구글에서 제공하는 결제API를 가져다 쓰기위해 샘플을 가져와서 디벼봤지만..

이건 언어는 자바가 맞는거 같은데 자바처럼 안보이는 그런느낌이랄까-_-;;


유니티로 개발하면서 네이티브 코드를 안만질수가 없다는 걸 다시한번 새삼 느끼는 계기가 된 거 같음..

원래는 Prime31을 알고는 있었지만 구매문제보다는 검색해봤을때 뭔가 사용에 문제가 있는거 같아서 사용을 안하다가 한 강사분께서 현업에서도 많이 쓰이는 에셋이라 하시길래 바로 구매후 사용해본 결과..


안드로이드를 모르는 사람이라 하더라도 엄청 쉽게 구글플레이 결제모듈 붙이는데 가능할것 같다.


자 그럼 시작해보자.

※ 구글인앱빌링 v3 버젼이 적용된 Prime31임을 참고.

※ Prime31을 임포트시킨후 빌드하여 구글 개발자 콘솔에서 apk를 업로드하면 Prime31의 구조상 플러그인안에 있는 매니페스트에서 안드로이드 접근 권한에 결제와 관련된 권한이 부여되어 있기 때문에 인앱상품 등록이 가능하다. 이는 따로 설명하지 않아도 검색하면 많이 있음. 기본적으로 인앱상품 등록이 가능하고, 구글개발자콘솔에서 하나 이상의 결제 테스트 계정이 있다고 가정하고 설명함을 참고.



1. 우선 필요한 것은 Prime31을 구매하는 것.. 구매후 임포트시키면 아래와 같이 구성된다.



2. 저기서 나에게 필요한 것은 demo 폴더안에 GoogleIABEventListener.cs 파일. 에셋의 특성상 소스 자체를 올리진 못하겠고 함수만 설명!


※ 먼저 결제 프로세스를 알아야하는데.. 일반적으로 구글플레이에서의 관리되지 않는 품목.. 즉 여러번 구매가 가능한 코인, 물약 같은 인앱상품의 경우 사용자가 결제를 하면 구입이 되고 이후 이를 소비를 시켜줘야 한다. 그렇지 않으면 한번 구매한 이후 해당 상품을 구매할 수 가 없다. 


- OnEnable()과 OnDisable()의 특징은 따로 설명하진 않겠음.. 검색하면 무수히 많은 정보가 있으니..ㅇ_ㅇ


billingSupportedEvent() : 결제를 진행할때 결제와 관련된 씬에 진입시 가장 처음에 초기화를 할텐데..(이 초기화 작업이 정확하게 왜 필요한지.. 하지 않을 경우 발생되는 일이야 추측은 가능하지만 디테일한 부분에 있어서 정확히 이해는 못했음 ㅠㅠ) 그 초기화가 완료된 이후 호출되는 함수다. 따라서 그에 따르는 처리를해야한다면 이 함수내에서 처리하면 될 거다.


billingNotSupportedEvent() : 이 함수명을 보면 알겠지만 위의 함수와는 반대의 이미다.. 초기화 실패시 호출된다.


- queryInventorySucceededEvent(), queryInventoryFailedEvent() : 개인적으로 쿼리는 DB에서만 만나본 개념이라-_-;;; 뭐 소비되지 않은 구입목록을 불러온 결과에 따라 자동 호출되는 개념으로 이해할 수 있을것 같다..


- purchaseCompleteAwaitingVerificationEvent() : 아이템을 구매한 이후 호출되는 함수다. 매개변수로 구입과 관련된 데이터와 서명(?)을 string 형식으로 받는데 이와 비슷한 다른 함수를 사용할 것이므로 일단 이 함수는 제껴두기로 ...(지금 함수명을 다시보니 인증과 관련되어 추가적인 액션이 가능한 것 같다.. 일단 쉽게쉽게 가기위해 패스하지만 결제인증과 관련된 부분이 있다면 다시 파고들 부분이다.)


- purchaseSucceededEvent() : 이 함수는 위의 함수와 동일하게 아이템을 구매한 이후 성공적으로 구매되었을 경우 호출되는 함수로 결제정보를 매개변수로 받는다. 이 결제정보엔 제품아이디, 영수증번호와 같은 정보가 있다.


- purchaseFailedEvent() : 함수명 보면 눈치 까야함..


- consumePurchaseSucceededEvent() : 소비형 아이템을 구매했다면 아이템을 구매한 다음 소비를 시켜야 하는데 이 소비가 성공적으로 되었다면 호출되는 함수로 만약 골드를 증가시켜줘야 한다 뭐 이런 아이템이라면 이 함수내에서 처리하면 되겠다.. 매개변수로 결제정보를 받는데, 이 결제정보안에 제품id가 있으므로 이 id를 비교해서 처리하면 되겠다..


- consumePurchaseFailedEvent() : 이거 역시 함수명으로 설명끝..



3. 위에서 설명한 GoogleIABEventListener.cs파일의 내용이 중요하므로 그 파일 그대로 가져다 써도 되고 파일의 내용을 복사해서 별도의 cs파일로 생성해도 된다. 여기선 별도의 파일로 뺀다.. 그냥 이건 내 성격상..ㄱ-;;; 위의 파일의 내용과 100%동일하게 BillingEventListener.cs라는 파일로 대체하였음..



4. BillingEventListener.cs를 결제와 관련된 씬에 빈 게임오브젝트를 만들고 BillingEventListener.cs 을 attach. (이 역시 결제와 관련된 씬 하이라키 내에 만드는 것은 기본..)



5.  결제와 관련된 씬을 로비라고 가정하고 설명한다면 로비 진입시 Start() 하나에서 결제를 초기화 시키는 작업을 해야한다.

내 경우는 결제와 관련된 액션은 BillingEventListener 라는 게임오브젝트에서 전부 관리할 거라 아까 만들었던 BillingEventListener.cs에 Start() 함수를 만들고 초기화 작업을 했음. 


초기화 작업은 GoogleIAB.init(publicKey)를 호출하면 되는데 여기서 publicKey는 구글 앱 상품관리 페이지에서 라이센스키를 의미함.



6. 초기화 작업이 정상적으로 되었을 경우 호출되는 함수에서 이를 판별해서 결제를 진행시키거나 결제를 막거나 혹은 게임을 종료시켜서 다시 시도하게 하거나 .. 이런 액션을 유도해야 하겠다..



7. 해당 작업이 완료되면 이제 실제 결제를 요청해볼 차례. 개발자 콘솔에서 인앱상품을 등록했다면 해당 페이지에서 상품번호를 사용해 아래와 같이 요청. (상품번호가 "0001" 이라고 가정)

GoogleIAB.productPurchase("0001");



8. 그럼 구글플레이 결제 프로세스가 진행되고, 결제완료시 purchaseSucceededEvent() 함수호출되는 것을 이용하여 아래와 같이 해당 아이템을 바로 소비시켜줘야한다.

GoogleIAB.consumeProduct("0001");



9. 소비 완료가 되면 그때 소비된 이후 프로세스를 각자 처리하면 되겠다..



사실 좀 더 정리하려고했는데 일단 이정도라도 이해가 가능할 정도라서.. 작업완료 후 결제인증과 관련된 추가적인 처리를 하고 나서 다시 업데이트를 하던가 해야할듯..




ps. 사실 5번 항목까지 적다가.. 결제 인증을 아무래도 붙여야 겠다 싶어서.. GoogleIAB.productPurchase()를 호출할때 매개변수로 developerPayLoad라는 스트링으로 함수오버로딩이 되어 있었다.. 원한 액션은 결제시 상품번호와 함께 토큰값을 전달하고 결제완료시 해당 토큰값이 정상인지 확인하는 절차를 넣고싶었으나.. 이부분은 테스트가 필요해서 일단 위에 기술하진 않았음..


사실 결제라는 부분이 민감한 부분이기때문에 인증은 반드시 필요한 부분이라.. 추후 이부분을 반드시 체크하고 넘어가야 하겠음..


이내용을 정리하기위해 참고한 일본쪽 사이트를 링크한다.

번역기 돌려서 보면 대충 이해할만하다..


http://westhillapps.blog.jp/archives/25210270.html



추가..

결제초기화 이후 purchaseCompleteAwaitingVerificationEvent() 함수와 연계하여 결제 인증을 처리할때 GoogleIAB.cs에 보면 setAutoVerifySignatures(bool a) 라는 함수와 함께 처리하면 될것 같다...



WRITTEN BY
빨강꼬마

,

public Camera _MyCamera;


void Start ()

 {

        UIRoot root = gameObject.GetComponent<UIRoot>();

  // 자동 조절

        root.automatic = true;

        

        root.manualHeight = 480;    

        root.minimumHeight = 480;

        root.maximumHeight = 1280;        

}


void Update ()

{

        float perx = 1024.0f / Screen.width;

        float pery = 768.0f / Screen.height;

        float v = (perx > pery) ? perx : pery;

  m_pCam.GetComponent<Camera>().orthographicSize = v;

}


WRITTEN BY
빨강꼬마

,