달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

C#의 GZipStream을 이용해 패킷을 압축해서 보내고 받고 테스트를 하니 잘된다.

그래서 unity에 적용!!!!! 하자 마자 오류 ...

유니티가 GZipStream과의 호환성 문제가 있나보다.

 

이것 저것 찾다가 sharpZipLib 라는걸 추천하길래 써봤다.

 

현재 서버의 구조는

 

1. 버퍼 생성

2. 버퍼의 데이터를 압축

3. 데이터를 암호화

4. 전송

 

1. 수신

2. 복호화

3. 압축 해제

4. 버퍼를 읽어 들임.

 

순서다.

 

압축 코드는 초 간단..

암호화와 압축이 옵션이라 호출 하는 쪽에서 버퍼를 공유하기 위해 out byte[] ret 으로 받고 있지만  그냥 애초에 리턴으 byte[]로 받는게 보통이다..

 

        private void Compress(byte[] data, out byte[] ret)
        {
            using(MemoryStream ms = new MemoryStream(data))  //  패킷 데이터를 넣어 메모리 스트림 생성 (input)
            {
                using(MemoryStream zip = new MemoryStream())  // 리턴받을 메모리 스트림 생성(output)
                {
                    BZip2.Compress(ms, zip, false, 3);  // input 스트림의 데이터를 output 스트림으로 압축하여 저장
                    ret = zip.ToArray();
                }
            }
        }

        public void Decompress(byte[] data, out byte[] ret)
        {
            using(MemoryStream ms = new MemoryStream(data))  // 패킷 데이터를 넣어 메모리 스트림 생성
            {
                using(MemoryStream zip = new MemoryStream()) // 리턴받을 메모리 스트림 생성
                {
                    BZip2.Decompress(ms, zip, false);  // 압축 해제 하여 output 스트림에 저장
                    ret = zip.ToArray();
                }
            }
        }

 

이렇게 데이터를 보내고 받으니 unity 에서도 잘 된다.

패킷 사이즈는 총 12000 byte 정도가 4000 byte 정도까지 줄어들었다.

 

 

Posted by SadDev
|

internal static ManualResetEvent recvDone = new ManualResetEvent(false);

....

public void Receive(Session session)
        {

session.Socket.BeginReceive(session.RecvMsg.stream, session.RecvMsg.ReadSize, (session.RecvMsg.stream.Length - session.RecvMsg.ReadSize), SocketFlags.None, new AsyncCallback(ReceiveCallback), session);

recvDone.WaitOne();

}

private void ReceiveCallback(IAsyncResult ar)
        {

recvDone.Set();

....
                this.Receive(session);

}

 

이와 같은 코드로 비동기 소켓 서버를 구현하였다.

 

그런데...

동시에 여러개의 패킷이 마구 날아오는 경우 최초 3~4개는 바로 바로 받는데 이후부터는 0.5초당 1개씩 패킷이 처리된다..

고민을 하다가  ManualResetEvent 를 사용안하고  IAsyncResult 의 WaitOne을 써보기로 했다.

 

Receive 함수의 비동기 리시브를

IAsyncResult ar = session.Socket.BeginReceive(session.RecvMsg.stream, session.RecvMsg.ReadSize, (session.RecvMsg.stream.Length - session.RecvMsg.ReadSize), SocketFlags.None, new AsyncCallback(ReceiveCallback), session);
                ar.AsyncWaitHandle.WaitOne();

이렇게 바꾸니.. 바로 바로 리시브 처리가 된다 ..

 

아우... msdn예제에서는 ResetEvent를 사용하던데.. IAsyncResult 로 바꾸니 잘되는 이유가 무엇인지 좀 찾아봐야겠다.

Posted by SadDev
|

이 망할 오류때문에 몇일을 고생한건지....

나에게 발생한 오류는 위와 같은 오류였다.


구글 검색을 통해

http://antonperez.com/2012/02/09/cannot-add-duplicate-collection-entry-of-type-error-in-iis-77-5/ 를 찾아냈고

defaultappPool.config 를 수정하라는 내용대로 수정을 하자 해결이 됐다.

하지만 문제는 IIS가 재시작 될때마다 해당 내용은 다시 써지고 반복이라는 것...


여러가지를 찾아보던 중 근본적인 config파일을 수정해야 될것 같다라는 생각에 설정을 찾다가 발견..

C:\Windows\System32\inetsrv\config\applicationHost.config 라는 파일이 있다.

이 파일에도 동일한 내용이 있으므로 여기서 수정을 하면 된다..


우선 해당 파일은 관리자 계정이 아니면 권한이 없다고 접근 금지가 되므로 파일을 다른곳에 복사 한 후 수정해서 다시 덮어쓰기 하면 된다.


내용은 <isapiFilters> 로 검색하면 

<isapiFilters>

            <filter name="ASP.Net_2.0.50727-64" path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv2.0" />

            <filter name="ASP.Net_2.0.50727.0" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv2.0" />

            <filter name="ASP.Net_2.0_for_V1.1" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv1.1" />

            <filter name="ASP.Net_4.0_64bit" path="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv4.0,bitness64" />

            <filter name="ASP.Net_4.0_32bit" path="C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv4.0,bitness32" />

        </isapiFilters>


 라는 내용과

<location path="Default Web Site">

        <system.webServer>

            <isapiFilters>

<filter name="ASP.Net_4.0_64bit" path="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv4.0,bitness64" />

            <filter name="ASP.Net_4.0_32bit" path="C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv4.0,bitness32" />

            </isapiFilters>

        </system.webServer>

    </location>


라는 내용이 나온다..

이중 두번째 내용을 삭제하여


<location path="Default Web Site">

        <system.webServer>

            <isapiFilters>

            </isapiFilters>

        </system.webServer>

    </location>


이렇게 변경하고 IIS 를 재시작 하면 해결 된다.



'프로그래밍 > ASP.NET' 카테고리의 다른 글

[ASP.NET] 게시판 페이징  (0) 2010.09.17
Posted by SadDev
|

MDI를 이용하여 프로그램을 짜다가 현재 활성화 된 자식폼의 함수를 호출하는 부분에 대한 문제가 발생했다.

MDI부모창에서 Child1 이라는 자식폼을 만든경우


child1 에서는 

public void 함수명()


Child1 child = (Child1)this.this.ActiveMdiChild;

child.함수명();


이라고 했으면 호출 되는게 당연하다.

MDI 에서 파일 저장하기를 하려고 하다가 문제가 발생했다.

자식폼이 하나가 아닌 것이다.. 자식폼이 여러개라면 ??

Child1인지 Child2 인지 모르기 때문에 child2 를 저장하는데 Child1을 선언했다면 오류가 날것이다.

그래서 인터넷을 뒤지다가 여러가지 정보를 모아서 성공!!!


우선 클래스 파일을 하나 만든다

MdiChildForm.cs 라고 하자.

public class MdiChildForm: Form   // fForm을 상속받자 (using System.Windows.Forms;)

    {

        public virtual void saveFile()   자식폼에서 호출될 함수 이름...  virtual로 선언.

        {

        }

    }


자식 폼에는 아래와 같이 함수를 추가 한다.


pubcli override void saveFile()

{

 .....내용 ....

}



MDI부모폼에서는 아래와 같이 사용한다.

private void Save_ToolStripMenuItem_Click(object sender, EventArgs e)

        {


            MdiChildFormchild = (MdiChildForm)this.ActiveMdiChild;

            child.saveFile();

        }


이로써 자식폼이 여러개일지라도 해당 자식폼의 함수를 호출 할 수 있게 되었다.




Posted by SadDev
|

서버와 클라이언트간의 데이터 전송을 하다보면 여러가지 일이 생긴다.

그중 보안적인 문제가 있는데 이를 위해 암호화를 시도!!

그 과정에서 만든 소스를 올려본다.


using System;

using System.IO;

using System.Security.Cryptography;


namespace DataCrypt

{

    public class Crypt

    {

        //Rijndael 암호화 

        private RijndaelManaged rmCrypt;

        private byte[] KEY;

        private byte[] IV;


        public Crypt()

        {

            rmCrypt = new RijndaelManaged();

        }


        /// <summary>

        /// 암호화 키

        /// </summary>

        public byte[] Key

        {

            get { return KEY; }

        }


        /// <summary>

        /// 암호화 벡터 값

        /// </summary>

        public byte[] Iv

        {

            get { return IV; }

        }

        

        /// <summary>

        /// 버퍼 데이터를 암호화 

        /// </summary>

        /// <param name="buffer">암호화할 데이터</param>

        /// <returns></returns>

        public byte[] Encrypt(byte[] buffer)

        {

            rmCrypt.GenerateKey();

            rmCrypt.GenerateIV();

            KEY = rmCrypt.Key;

            IV = rmCrypt.IV;

            return EncryptBytes(buffer);

        }


        /// <summary>

        /// 버퍼 데이터를 복호화

        /// </summary>

        /// <param name="buffer">복호화 할 데이터</param>

        /// <param name="key">암호화 키</param>

        /// <param name="iv">암호화 벡터 값</param>

        /// <returns></returns>

        public byte[] Decrypt(byte[] buffer, byte[] key, byte[] iv)

        {

            rmCrypt.Key = key;

            rmCrypt.IV = iv;

            return DecryptBytes(buffer);

        }

       

        /// <summary>

        /// 암호화 함수

        /// </summary>

        /// <param name="data">암호화 할 데이터</param>

        /// <returns></returns>

        private byte[] EncryptBytes(byte[] data)

        {

            if (data == null || data.Length == 0)

            {

                return data;

            }


            if (rmCrypt == null)

            {

                throw new ArgumentNullException("rmCrypt");

            }


            using (MemoryStream stream = new MemoryStream()) 

            using (ICryptoTransform encryptor = rmCrypt.CreateEncryptor())

            using (CryptoStream cryptStream = new CryptoStream(stream, encryptor, CryptoStreamMode.Write))

            {

                cryptStream.Write(data, 0, data.Length);

                cryptStream.FlushFinalBlock();

                return stream.ToArray();

            }

        }


        /// <summary>

        /// 복호화 함수

        /// </summary>

        /// <param name="data">복호화 할 데이터</param>

        /// <returns></returns>

        private byte[] DecryptBytes(byte[] data)

        {

            if (data == null || data.Length == 0)

            {

                return data;

            }


            if (rmCrypt == null)

            {

                throw new ArgumentNullException("rmCrypt");

            }


            using (MemoryStream stream = new MemoryStream())

            using (ICryptoTransform decryptor = rmCrypt.CreateDecryptor())

            using (CryptoStream cryptStream = new CryptoStream(stream, decryptor, CryptoStreamMode.Write))

            {

                cryptStream.Write(data, 0, data.Length);

                cryptStream.FlushFinalBlock();

                return stream.ToArray();

            }

        }        

    }

}



사용법은 간단하게

Crypt crypt = new Crypt();

byte[] buffer = crypt.Encrypt(tmpBuffer);

식으로 하면 암호화 된 데이터가 넘어온다..

복호화의 경우는  key와  iv가 필요하다.

byte[] buffer = crypt.Decrypt(tmpBuffer, key, iv);




Posted by SadDev
|

c 에서 사용하는 pointer 구조처럼 C#의 BinaryWriter에 사용하는법

MemoryStream ms = new MemoryStream();

BinaryWriter bw = new BinaryWriter(ms);

 

int a = 400;

bw.write(&a);

라는 형태로 메모리  pointer를 C#에서는 사용할수가 없다.

 

그래서 알아보니 bw.seek() 을 쓰면 된다고 해서 써봤다..

 

int a = 400;

int b = 500;

int c = 600;

int d = 999;

int e = 700;

bw.write(a);

long pointer = bw.BaseStream.position;

bw.write(b);

bw.write(c);

bw.seek((int)pointer, SeekOrigin.Begin);

bw.write(d);

bw.seek(0, SeekOrigin.End);

bw.write(e);

 

결과

400

999  //b가 500 이지만 pointer 위치를 다시 덮어 씌워서 d 를 넣었음로 999가 들어가게된다.

600

700

 

우선 이렇게 해서 해결!

Posted by SadDev
|
index.php

$('#file_upload').uploadify({
   'uploader'  : './swf/uploadify.swf',
   'script'    : './common/uploadify.php',
   'cancelImg' : './images/cancel.png',
   'folder'    : './uploads<?=$path?>',
   'multi'     : true,
   'fileExt'   : '*.jpg;*.jpeg;*.png;*.gif',
   'onComplete'  : function(event, ID, fileObj, response, data) {
    var ret = response.toString().split('|');
    if(ret[0] != 0){
     alert("Err : " + ret[0] + "\n" + ret[1]);     // ret[0] = 오류 코드 ,  ret[1] = 파일명     uploadify.php 에서 받은 리턴값
    } else {
     $("#filename").val(ret[1]);
          $.ajax({
           url : './common/upload.php',
           type : 'POST',
           dataType : 'json',
           data : $('form').serialize(),
           timeout : 5000,
           error : function(xhr){
            alert('DB 저장 중 오류가 발생하였습니다.\n' + xhr.responseText);
           },
           success : function(xhr){
       getThumbnailList(pathid);
           }
          });
    } 
      }
  });



<form name="frmFile" id="frmFile" method="POST" action="common/upload.php">
    <input type="hidden" id="filename" name="filename" value="" />
    <input type="hidden" id="pathID" name="pathID" value="<?=$pathid?>" />
    <input type="hidden" id="pathDir" name="pathDir" value="uploads<?=$path?>" />
    <div id="uploadBtn_layer"><input type="button" id="btnStartUpload" value="업로드 시작"/></div>

    <input type="file" name="file_upload" id="file_upload" />
   </form>

pathid 나  pathDir 은 DB에서 읽어온 정보를 넣는 것이므로 개인적인 작업이라 무시하시고 수정해서 사용 가능

jquery.uploadify.xx.js 파일  에서
g.folder=escape(f.folder); 를 찾아서
g.folder=encodeURIComponent(f.folder);  로 수정


Uploadifiy.php

if (!empty($_FILES)) {
  $tempFile = $_FILES['Filedata']['tmp_name'];
  $targetPath = $_SERVER['DOCUMENT_ROOT'] . urldecode($_REQUEST['folder']) . '/';    ///* 폴더 명을 urldecode 로 받는다.  */;
  $filename = iconv("UTF-8", "EUC-KR", $_FILES['Filedata']['name']);    // /* 파일 명을 urldecode 로 받고  iconv 로 처리 */
  $targetFile =  str_replace('//','/',iconv("UTF-8", "EUC-KR", $targetPath)) . $filename;   // /* 폴더명도 iconv 처리  */
  
   if(file_exists($targetFile)){
   echo "-200|overlap file name. [".iconv("EUC-KR", "UTF-8", $filename)."]";    // 이미 파일이 존재할  경우 -200cjfl
  } else {
   move_uploaded_file($tempFile, $targetFile);
   echo "0|".iconv("EUC-KR", "UTF-8", $filename);     // 업로드 성공하면 0
  }
 }
 else
 {
  echo "-100|Uploaded file is empty.";      // 파일이 없으면 -100 처리
 }
uploadify.php 에서 성공 처리 되면  upload.php 를  호출해서 DB에 데이터를 넣는다 이때 중요한것은 uploadify.php 에서 리턴시 다시 iconv("EUC-KR", "UTF-8", $filename)로
반대로 인코딩 해서 보내야 정상적으로 처리된다.. serialize 자체가 encodeURIcomponent() 가 붙다보니.. 인코딩 디코딩 부분이 까다롭다.

Upload.php

$filename = urldecode($_REQUEST['filename']);   // file명은 꼭 urldecode로
 $pathId = $_REQUEST['pathID'];
 if($pathId == "") $pathId = 1;
  $fullpath = "gallery/uploads";
 $path_result = mysql_query("select * from path where pidx = $pathId") or die(mysql_error());
 while($path_row = mysql_fetch_array($path_result)){
  $fullpath = $path_row[FULLPATH];
 }
 
 $targetFile =  $_SERVER['DOCUMENT_ROOT'] . $lib->upload_dir . iconv("UTF-8", "EUC-KR", $fullpath) . iconv("UTF-8", "EUC-KR", $filename);   // 경로명은 DB에서 가져와서 iconv 만 했지만 파라미터로 받을 경우 urldecode 후 iconv
 $ext = substr(strrchr($targetFile, '.'), 1);
 $filesize = filesize($targetFile);
 $width = 0;
 $height = 0;
 if(strtolower($ext) == "jpg" || strtolower($ext) == "gif" || strtolower($ext) == "png") {
  list($width, $height) = getimagesize($targetFile);
 }
 $now = date("Y-m-d h:i:s", time());
 //$filename = iconv("EUC-KR", "UTF-8", $filename);
 $file_query = "insert into Files (filename, pidx, extension, width, height, filesize, regdate)
  values('$filename','$pathId','$ext','$width','$height','$filesize','$now')";
 
 mysql_query($file_query) or die(mysql_error());



대충 문제가 됐던 것은 php 에서 리턴받은 한글 파일을 다시 다른 페이지로 넘기는 과정에서 한글 문제가 있었다.
어쨋든 폴더명이 한글이던 파일명이 한글이던 잘 올라간다.





Posted by SadDev
|
php 로 EXIF의 keyword 값(이미지 정보중 태그 값) 을 수정하려다가 포기하고 C#으로 만들어 봤다...

몇가지 사이트에서 정보를 얻어서 했지만 자꾸 문제가 발생...
byte 단위로 데이터 읽어가면서 테스트 해서 겨우 성공..

  protected void Page_Load(object sender, EventArgs e)
        {
 //* 특정 이미지 파일을 읽어와서 이미지의 EXIF 정보 중 40094(keyword) 를 화면에 출력함. *//
            System.Drawing.Image theImage = new Bitmap(@"D:\\photo\\DSC02787.JPG");   

            System.Drawing.Imaging.PropertyItem[] propItems = theImage.PropertyItems;
            foreach (System.Drawing.Imaging.PropertyItem items in propItems)
            {
                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
//                string value = encoding.GetString(items.Value);
                if (items.Id == 40094)
                {   
                    Response.Write(items.Type + " : " + items.Id + " = " + items.Value.ToString() + "(len : " + items.Len + ")");
                   // for (int i = 0; i < items.Value.Length; i++) Response.Write(items.Value[i] + "<br>");
                }
            }
            theImage.Dispose();
// 특정 이미지에 keyword 값을 수정하여 이미지를 새로 저장
            WriteNewDescriptionInImage(@"d:\\photo\\DSC02787.JPG", "지혜님짱!!;이쁜이;사랑스러워");
        }

        private static ImageCodecInfo GetEncodeInfo(string mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; j++)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

        private void WriteNewDescriptionInImage(string FileName, string NewKeywords)
        {
            System.Drawing.Image pic;
            PropertyItem[] PropertyItems;
            byte[] bKeywords = new Byte[NewKeywords.Length];

            string FileNameTemp;
            System.Drawing.Imaging.Encoder Enc = System.Drawing.Imaging.Encoder.Transformation;
            EncoderParameters EncParmas = new EncoderParameters(1);
            EncoderParameter EncParam;
            ImageCodecInfo CodenInfo = GetEncodeInfo("image/jpeg");
// 여기가 중요함 keyword 의 경우 unicode로 읽어야 함... comment 의 경우  ASCII나 UTF8로 읽어야 함. 
            bKeywords = Encoding.Unicode.GetBytes(NewKeywords);

            pic = System.Drawing.Image.FromFile(FileName);
            PropertyItems = pic.PropertyItems;
            foreach (PropertyItem item in PropertyItems)
            {
                if (item.Id == 40094)
                {
                    item.Value = bKeywords;
                    item.Len = bKeywords.Length;
                    pic.SetPropertyItem(item);
                }
            }

            FileNameTemp = FileName + ".temp";  // 파일명 + .temp로 새로 저장...
            EncParam = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate90);
            EncParmas.Param[0] = EncParam;

            pic.Save(FileNameTemp, CodenInfo, EncParmas);
            pic.Dispose();
            pic = null;
            GC.Collect();
        }

'프로그래밍 > C#' 카테고리의 다른 글

[C#]byte[] 데이터 암호화  (0) 2012.08.31
BinaryWriter pointer 사용(seek)  (0) 2012.08.06
[C#] DataGridViewRow 복사  (0) 2010.07.05
[C#] MessageBox의 기능  (1) 2010.07.05
[C#] DataGridView  (0) 2010.07.02
Posted by SadDev
|
윈도우 라이브 라이터로 Textyle에 글을 작성할 경우 이미지 파일이 깨져 보이는 현상이 발생..

원인을 찾아보니..

라이브 라이터에서 aaa.jpg 라는 파일을 첨부하면
본문에는 <img src="$aaa.jpg"> 라고 태그가 생성되어 본문이 작성된다..

하지만 XE 에서는 파일 업로드 시 base64 인코딩을 해서
asdgqerhq34gh35hgq3gq3h.jpg 와 같이 암호화 된 파일명으로 변경되게 된다..

그러다 보니 본문에서 aaa.jpg 를 불러오려 해봤자 파일을 찾을 수 없어 엑박이 뜨게 된다.

이를 해결하기 위해 아래와 같이 수정했다.

1. /xe/addons/blogapi/blogapi.addon.php 의 line 208 정도에

case 'metaWeblog.newPost':    라는 곳이 있다.   새글 작성할 경우 이곳에서 처리가 되는데.. 이중에
첨부 파일 정리를 하는
대충 line 258 정도에
$oFileController->insertFile($file_info, $this->module_srl, $document_srl, 0, true);   라는 곳이 있다.
이곳을
$oFile = $oFileController->insertFile($file_info, $this->module_srl, $document_srl, 0, true);  로 바꾼다...그리고 바로 아랫줄에
$obj->content = str_replace($file_info['name'], $oFile->get('upload_filename'), $obj->content); 라고 추가 해준다.
설명 하자면... 
 $oFileController->insertFile() 결과를 $oFile 변수에 담는다....
그리고 두번째 줄은 본문 내용은 파일 네임이 들어간 부분을(aaa.jpg 라고 되어 있는 부분을)  암호화된 파일명으로 교체(replcae) 시키다.
이로써 본문의 내용중 <img src="aaa.jpg">는 <img src="asdgqerhq34gh35hgq3gq3h.jpg"> 로 바뀌게 된다.

2. 게시물을 수정할 때는 기존에 올렸던 파일을 다시 첨부하는게 아니고 본문 내용만 그대로 가져가므로 본문 내용중 파일 이름과 일치 하는 곳은 수정해야 된다.
line 283 쯤에
case 'metaWeBlog.editPost' : 라는 곳을 수정한다.

line 366 쯤에 $obj->content = str_replace( ......생략   이런 부분이 있는데.. 바로 위에 아래 내용을 추가 한다.

$oFileModel = $getModel('file');
$file_list = $oFileModel->getFile($document_srl);  // 현재 게시물에 있는 첨부 파일 리스트를 가져온다.
if(count($file_list)){  // 첨부 파일이 있다면
 foreach($file_list as $file)
{
  $obj->content = str_replace($file->source_filename, $file->upload_filename, $obj->content);  // 본문 중 실제 파일명과 같은 부분을 암호화 된 파일명으로 변경
}
}

이렇게만 수정해 주면 끝...

다만 윈도우 라이브 라이터가 한글 파일을 지원하지 않는다.. 
Posted by SadDev
|
Posted by SadDev
|