오랜만에 글 하나 끄적여 봅니다.

 

예전부터 세계최고 게임회사 콦낪밊 아케이드 복돌 데이터가 풀리면 "돈나미 보안 좆*이네! ㅋㅋㅋ" 생각하면서 살던 시절이 있었습니다.

나중에 알게 되었지만 좆*인건 당연히 필자였습니다.

리버스 엔지니어링 지식은 전혀 없다보니 "일단 해보고 보자"라는 생각으로 개고생 많이 했습니다.

그래서 실패기입니다...

 

mon님이 쓰신 글을 보고 DRM 해제를 시도 했습니다.

https://mon.im/2017/12/konami-arcade-drm.html

 

Unravelling Konami's Arcade DRM

It’s well known that Konami doesn’t have complex protection on its arcade systems. Programmed World was a fan-made network replacement and update service for Konami arcade games with no support outside of Japan. Nothing of its scale and quality had bee

mon.im

 

 

아케이드 HDD, USB 인증 토큰은 야후 재팬 옥션 구매 대행 사이트 통해 구했습니다.

https://auctions.yahoo.co.jp/closedsearch/closedsearch?p=konami+HDD+%28konami%29&va=konami+HDD&vo=konami&b=1&n=60 

 

ヤフオク! -「konami HDD (konami)」の落札相場・落札価格

この広告は次の情報に基づいて表示されています。 現在の検索キーワード 過去の検索内容および位置情報 ほかのウェブサイトへのアクセス履歴

auctions.yahoo.co.jp

 

 

혹시나 구매하려는 분들이 계신다면 굳이 PCB 본체까지 사지 마시고 아케이드 HDD, USB 인증 토큰만 있어도 어느 정도 구동이 가능해서 복호화 과정에는 문제가 없다고 생각합니다.

그리고 실제 오락실 장비에서 몰래 가져와서 작업하시면 큰일 납니다.

 

 

제가 구매한 리스트에는 최신 게임부터 구형 게임까지 다양하게 구매해서 확인 해본 결과 최신 게임은 mon님 글과 많이 다르기도하고 64비트이므로 필자 실력으로는 IDA 분석에 어려움이 있어 리플렉비트로 작업 했습니다.

 

하드 받자마자 제일 처음 했던건 고스트 백업입니다.

SelfImage 사용하셔도 되고 어떻게든 하드는 미리 백업 하시는게 좋습니다.

 

기본 파티션의 수는 4개입니다.

 

C : OS

konami 폴더에는 게임 시작에 필요한 기본적인 배치 파일과 유틸리티 파일이 있습니다.

가상머신에서 작업하시는 분들은 "klock.exe" 파일에 이름을 바꿔주시고 부팅하시면 키보드가 동작하게 됩니다.

D : 게임 데이터

아마 복돌 데이터 받으면 간혹 0~d 폴더가 있는 경우가 있는데 암호화된 게임 데이터가 들어있는 파티션입니다. 

나머지 파티션 및 자세한 사항은 아래 글을 보시면 됩니다.

https://www.zanneth.com/2015/05/03/konami-game-data.html

 

 

처음으로 해야하는 작업은 D:\MBR\contents\modules\ DLL 복호화 및 분석입니다.

 

테스트 환경 - VMware Windows 7 32bit 

SafeNet 드라이버 설치 후 동글 2개 연결 해주고 게임 실행 해봤습니다.

 

D:\MBR\contents>modules\bootstrap.exe prop\bootstrap.xml env_drm prop\trustcerts.p7s prop\trustfiles.p7s
M:boot: bootstrap 1.5.1  build at 2012-07-10 14:57:21+9, r610
M:boot: Copyright (C) 2008-2012 KONAMI Digital Entertainment Co.,Ltd.
M:adb: attached successfully.
F:boot: The process must be shutdown due to the security consideration.
M:adb: bootstrap.exe has been exit.

 

당연하지만 보안 어쩌고 하면서 게임이 종료됩니다.

이제 어느 부분에서 문제가 됐는지 확인이 필요합니다.

IDA 디컴파일러로 확인 해보니 The process must be shutdown due to the security consideration. 메시지 위치까지 디버깅하면서 어느 부분이 문제인지 찾아 보기로 했습니다.

 

IDA 디버깅 해보니 이번에는 다른 오류가 발생합니다.

따라가보니 kbd.exe 프로세스 생성 후 kbd.exe 메시지에서 추정되는 bootstrap.exe 죽이는 코드가 동작하게 됩니다.

 

mon님 글에서 bootstrap.exe <- kbd.exe 디버거로 연결 된다고 합니다.

즉 IDA에서 이미 디버깅 상태이므로 문제가 생기니 kbd.exe 프로그램을 새로 만들어 넣으라 합니다.

#include <stdio.h>
#include <windows.h>

int main() {
    printf("I:adb proxy launched\n");
    while(1)
        Sleep(10000);
    return 0;
}

 

프로그램 빌드 후 넣고 다시 디버깅 해봤습니다.

하지만 새로운 문제가 발생하게 됩니다.

디버깅으로 천천히 따라가면 kbd.exe 변조 프로그램으로 감지 되어 "Unable to initialize security process." 메시지와 함께 프로그램이 강제 종료 됩니다.

 

mon님이 언급하신 디버깅 메시지 출력과 서명 우회가 필요한 순간이었습니다.

WriteProcessMemory 코드는 워낙 자료가 많아 C# 코드로 대충 짜서 적용 시키고 다시 디버깅 해봤습니다.

디버깅 메시지 출력하는 코드가 여러 곳이라 어느 부분이 맞는지 찾기 귀찮아 모두 90, 90, 90.... 값으로 덮어 씌우고 서명 부분은 74->75 값을 바꿔 해결 했습니다. 

사실 이게 맞는건지 잘 모르지만 동작은 됐습니다. ㅎㅎ...

더 좋은 방법 있으면 댓글 조언 부탁드리겠습니다.

 

디버깅 메시지가 출력되고 서명 우회가 잘 동작하는지 자세하게 로그가 남게 되었습니다.

 

이대로 쭉 실행하면 오류가 발생하면서 프로그램이 멈추게 됩니다.

여기서 자세히 보면 kdn.dll, kbt.dll, kmp.dll, kma.dll, kdu.dll, libavs-win32.dll, libavs-win32-ea3.dll 파일이 로드 되었다고 메시지가 나옵니다.

어느 순간 DLL 일부가 복호화가 되었다는 이야기입니다.

 

다음 글에서는 메모리에 복호화된 DLL 추출 방법으로 다시 찾아오겠습니다.

 

그럼 20000~

시작하기전에 유니코드에는 BOM이 필요 없다고 울부짖는 분들에게 분노의 글 좀 적겠습니다.

전부터 무언가 만들어 볼 때 마다 항상 제 발목을 붙잡는 녀석이 있었습니다. 그게 바로 인코딩이죠.
Linux에서는 UTF-8이 기본이니까 그냥 UTF-8로 열어서 보여줘버리면 그만인데 Windows는 그게 아니란말이죠.

세상에는 여러 인코딩이 존재합니다. 그런 인코딩의 존재를 깡그리 무시해버리고 'UTF-8이 진리다'를 외치며 BOM을 달지도 않고 텍스트를 배포하시는 불친절한 분들 덕분에 언제나 우리는 고생합니다. BOM을 달면 유니코드 계열은 8 16 32 등등 을 아주 쉽게 구분할 수 있는데도 불구하고 그 3바이트를 아낀다고 아무도 저장하지 않아요. 이 얼마나 무식한 짓인지;

저 BOM을 달지 않아서 벌어지는 일들을 볼까요?
자주 나오는 글자들의 각 인코딩별 저장되는 비트값을 확인하여 "확률"적으로 이 텍스트 파일은 UTF-8이다 16이다를 계산하고 앉아있습니다;
3바이트만 체크하면 끝나는걸;
더 나아가볼까요?
모질라에서 만든 캐릭터 디텍터라던가 모두 제대로 작동할 것 같나요? 글자 수가 적으면 적을수록 감출 확률은 떨어지고 그 만큼 실패할 확률도 올라갑니다.
그 뿐인가요? 그 캐릭터 디텍터는 누가 개발하나요. 라이브러리 주워다가 쓰면 가능하지만 그게 제대로 작동하는 라이브러리인지 아닌지는 테스트 하기 전까지는 모릅니다. 그 수 많은 라이브러리 테스트해서 사용하실려구요? BOM 달면 3바이트만 체크하면 되는데? 여러분의 하드는 2.5인치 플로피 디스켓 보다 작아서 3바이트를 저장하면 큰일인가요?

아직도 BOM이 필요 없다고 주장하실 생각이신가요? '이 세상에 워드프로세서따위 한글과 컴퓨터에서 개발한 한글밖에 없으면 모두가 hwp파일을 쓸테니 걱정이 없다.' 와 같은 의견으로 들릴 뿐입니다.

그런관계로 여러분 제발 유니코드엔 BOM좀 달아주세요. 제발 이게 세계 표준이되길 바랍니다. 힘들거든요 -ㅅ-;

자 그래서. 시작해보죠.

ASP.Net에서 이 인코딩 디텍터를 사용할 일이 있는데 C#에서 COM+쓰는 방법을 모르겠더라구요...
그래서 조금 찾아보려다가 그냥 C++/CLI로 만든 다음 ASP.Net 프로젝트에 참조 추가했습니다;

ASP.Net에서 C++/CLI로 만든 dll을 사용하는 방법은 다음에 써보겠습니다. 근대 무지 쉽습니다.

Windows는 XP의 서비스팩 몇인진 기억이 안나지만(...) 쨋든 이후로 MLang라는 녀석을 제공해주고 있습니다.
인코딩 관련 COM+ 라이브러리인데 굉장히 잘 작동해서 맘에듭니다. 이 녀석을 써보도록 하죠.


#include <MLang.h>
using namespace System;

namespace EncodingUtility {
	public ref class EncodingDetector
	{
	public:
		EncodingDetector();
		~EncodingDetector();

		int GetEncodingFromFile(String^ fileName);
		int GetEncodingFromBinary(char* binary, int size);

	private:
		IMultiLanguage2* mlang2;
	};
}


헤더파일입니다. 무지간단하네요; 바로 cpp코드를 보죠.


#include "EncodingDetector.h"

using namespace System::IO;
using namespace System::Text;

EncodingUtility::EncodingDetector::EncodingDetector()
	: mlang2(nullptr)
{
	IMultiLanguage2* tmpMlang = nullptr;
	auto result = CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_ALL, IID_IMultiLanguage2, (void**)&tmpMlang);
	mlang2 = tmpMlang;

	if (result != S_OK)
		throw gcnew System::Exception("에러");
}

EncodingUtility::EncodingDetector::~EncodingDetector()
{
	if (mlang2 != nullptr)
		mlang2->Release();
}

int EncodingUtility::EncodingDetector::GetEncodingFromFile(String^ fileName)
{
	FileStream^ fs = gcnew FileStream(fileName, FileMode::Open, FileAccess::Read, FileShare::Read);
	BinaryReader^ br = gcnew BinaryReader(fs);
	
	// 필요하시다면 아래 4096 사이즈를 더 높여주세요. 많이 읽을 수록 검출율은 높아집니다.
	auto buffer = br->ReadBytes(4096);
	pin_ptr<System::Byte> pinBuffer = &buffer[0];
	unsigned char* pBuffer = pinBuffer;

	int length = buffer->Length;
	DetectEncodingInfo info;
	int score = 1;
	HRESULT result = mlang2->DetectInputCodepage(0, 0, reinterpret_cast<char*>(pBuffer), &length, &info, &score);

	fs->Close();
	if (result == S_OK)
		return info.nCodePage;
	else
		return -1;
}

int EncodingUtility::EncodingDetector::GetEncodingFromBinary(char * binary, int size)
{
	int length = size;
	DetectEncodingInfo info;
	int score = 1;
	HRESULT result = mlang2->DetectInputCodepage(0, 0, binary, &length, &info, &score);

	if (result == S_OK)
		return info.nCodePage;
	else
		return -1;
}


역시 간단하네요. COM을 사용하니까 클래스 사용 전 CoInitialize() 함수 호출하시는거 잊지 마시구요!

DetectInputCodepage에 관한 자세한 내용은 MSDN을 참고해주시기 바랍니다.

대략적으로 이 함수는 한번에 여러 개의 인코딩을 리턴 할 수 있습니다.
왜 여러개를 리턴하냐구요? BOM이 없는 텍스트 파일이 있기 때문이죠 ㅡㅡ;
지금 위의 코드에서는 가장 확률이 높은 1개만 가져오게 해놨지만 BOM이 없을 경우 UTF-16일 수도 있고 UTF-8일 수도 있기 때문에 가장 확률이 높은 인코딩부터 순서대로 인덱스에 넣어주는 모양입니다.

코드페이지에 대한 결과 역시 MSDN을 참고해주세요. 윈도우 개발자는 MSDN이 있어서 행복합니다...
https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx


C#에서는 Encoding.GetEncoding(GetEncodingFromFile("test.txt")) <- 이런 식으로 사용하실 수 있습니다.
감지 실패시 -1을 리턴하므로 예외처리 해주시면되겠습니다.

뭐 이건 간단하게 만들어본 SafeComInitializer이라는 클래스입니다.
헤더는 올릴 필요가 없어보이므로 그냥 cpp 코드만 올리겠습니다.


#include "SafeComInitializer.h"

bool SafeComInitializer::ComInitialize()
{
	static SafeComInitializer initializer;
	return initializer.isInitialized;
}


SafeComInitializer::~SafeComInitializer()
{
	if (canUninitialize == true)
		CoUninitialize();
}


SafeComInitializer::SafeComInitializer()
	: isInitialized(false), canUninitialize(false)
{
	if (isInitialized == false)
	{
		auto result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
		if (result == S_OK)
		{
			isInitialized = true;
			canUninitialize = true;
		}
		else if (result == S_FALSE)
		{
			isInitialized = true;
			canUninitialize = false;
		}
	}
}


How to compile apps using Xcode 7 to run on a non-jailbroken device 글을 기반으로 작성 했습니다.


탈옥하지 않고 순정에서 무료 계정으로 APP 설치가 가능해졌습니다.

전에는 개발자 유료 계정에 가입이 필요했으나 지금은 그 제한이 사라진 덕분이죠


먼저 Xcode 7 설치된 맥과 iOS 기기가 필요합니다.

Provenance 소스는 여기에 있습니다.

https://github.com/jasarien/Provenance


HTTPS clone URL 주소를 복사합니다.

https://github.com/jasarien/Provenance.git


Xcode 실행을 하시고 Source Control - Check Out 선택



스크린샷처럼 Provenance GIT 주소를 넣어주고 Next, master 누르고 또 Next


 

스크린샷에 그림대로 추가가 될텐데 Bundle Identifier 부분을 바꿔줍니다.

com.jamsoftonline.Provenance -> com.myname.Provenance , 대충 본인 영어 닉을 적어줍니다.

Team 부분은 Unknown name이 아닌 본인 계정으로 바꿔주시고 Fix Issue 누르면 일단 빌드를 위한 준비가 끝납니다.

계정이 없는 분들은 Add an Account 눌려서 계정을 추가하고 위에 Team 부분을 하시면 됩니다.


마지막으로 아이패드를 연결 하신 뒤 빌드를 누르면 끝!



전 iOS 8 , 아이패드 미니2 에다가 올려서 실행을 해보니 잘 되네요.


아 오랜만에 글을 써봅니다! 그 동안 얼마나 글을 쓰고싶었는지!
안녕하세요 저는 잘 지냅니다 허허.

오늘 들고온 물건은 역시 잉여한 물건입니다. 저는 이런 잉여한 작업을 좋아하죠.
저는 참고 문헌을 마지막에 쓰는게 싫습니다. 먼저 쓰신 분들에게 실례같아요. 그러니 먼저 오늘 이 글을 쓰기에 이르기까지 참고한 사이트를 먼저 쓰고자 합니다.

http://www.clien.net/cs2/bbs/board.php?bo_table=lecture&wr_id=212575
http://www.linuxjournal.com/article/3641
https://docs.python.org/3.4/c-api/init.html

자 그럼 시작해볼까요.

이번 글은 C++에서 tweepy를 사용하기 위해 삽질한 글 입니다. 사용한 파이썬 버전은 3.4.3 입니다.
최종 목적은 C 함수를 파이썬에서 호출하고 파이썬 코드를 C에서 호출하는 것 입니다.

이 글은 Windows를 기준으로 설명됩니다. 리눅스는 훨씬 더 쉽게 작업을 진행 하실 수 있습니다 -ㅅ-; 리눅스 개발자 분들은 알아서 잘 하실거라 믿어요.

먼저 파이썬을 다운받아 설치합니다. 아, 최종적으로 배포하시려는 프로그램의 플랫폼이 32비트면 32비트 파이썬을 다운받아 주시는것 잊지 마시구요!
이제 부스트를 빌드해봅시다. 부스트 빌드 옵션의 자세한 설명은 구글에 검색해 보시길 권장합니다. 참고로 저는 아래 블로그를 참고해서 빌드했습니다.

http://warmz.tistory.com/903

그 다음에 tweepy를 받고 인스톨합니다. 머 이건 자유입니다. 그냥 참고만 하셔도 됩니다. (참고로 tweepy는 3.4.3에서 오류가 있습니다. 해결 방법은 맨 마지막에 있습니다.)

위 작업이 끝나셨으면, 이제 코딩하러 갑니다.
VS를 실행하시고 프로젝트 생성 후 반드시 Release 빌드로 바꾸시고 Python 인스톨 디렉터리 안의 include를 헤더 파일 경로에 추가, libs 폴더를 라이브러리 폴더에 추가해줍니다.
Release로 빌드 안하면 python_d.lib 없다고 컴파일 안되는데 python_d.lib를 구하려면 파이썬 소스 코드를 직접 컴파일 하셔야합니다... 귀찮으니 생략합니다.

부스트 역시 라이브러리 폴더와 헤더 폴더에 추가합니다. (경로는 부스트 빌드가 끝나면 출력해줍니다.)

으음. 일단 파이썬 스크립트먼저 대충 짜볼까요...
tweepy 예제 코드를 조금 수정해보죠.

import twit_c
import time
import webbrowser
from getpass import getpass

import tweepy
import sys

class StreamWatcherListener(tweepy.StreamListener):
    def on_status(self, status):
        try:
            # C 함수를 호출합니다.
            twit_c.on_status(status.text)
        except:
            print(sys.exc_info())
            pass

    def on_error(self, status_code):
        print('An error has occured! Status code = %s' % status_code)
        return True  # keep stream alive

    def on_timeout(self):
        print('Snoozing Zzzzzz')

consumer_key = '수정'
consumer_secret = '수정'

access_token = '수정'
access_secret = '수정'

auth = tweepy.auth.OAuthHandler(consumer_key, consumer_secret, 'oob')
auth.set_access_token(access_token, access_secret)

api = tweepy.API(auth)
stream = ''

l = StreamWatcherListener()

def get_authorization_url():
    return auth.get_authorization_url()

def set_access_token(pin):
    auth.get_access_token(verifier=pin)
    
    global api
    api = tweepy.API(auth)
                
def userstream_start(run_async = False):
    global stream
    global l
    stream = tweepy.Stream(auth, l)
    stream.userstream(async = run_async)

def userstream_end():
    global stream
    stream.disconnect()

def home_timeline(page = 0, max_id = -1):
    if max_id == -1:
        return api.home_timeline()
    else:
        return api.home_timeline(page=0, max_id=max_id);

으음. 머 이정도 일까요. 제가 파이썬을 제대로 공부한게 아니라 코드가 병신이네요. 넹 머 그래도 불편함 없이 충분히 테스트 가능할거 같네요.
프로젝트 폴더 안에 scripts라는 폴더를 생성 하고 위 코드를 twit.py 라는 파일로 저장하겠습니다. 그리고 __init__.py 를 만듭니다. 머 딱히 아무것도 안쓰셔도 됩니다.


C++로 돌아와보죠.
부스트 헤더와 파이썬 헤더를 인클루드합니다.

#include <stdio.h>
#include <string>
#include <windows.h>

#include <python.h>

// 아래 두 개의 전처리문에 대해서는 검색하시기 바랍니다.
#define BOOST_ALL_NO_LIB 
#define BOOST_PYTHON_STATIC_LIB 
#include <boost/python.hpp>

// 사용할 lib 파일을 지정합니다.
#pragma comment(lib, "libboost_python3-vc120-mt-1_58.lib")

// 유니코드 지원을 위한
#include <io.h>
#include <fcntl.h>

자 이제 파이썬에서 호출할 함수를 만들죠.

// 파이썬에서 호출할 함수입니다.
static PyObject* OnStatus(PyObject *self, PyObject *args)
{
	const char* str = NULL;
	if (!PyArg_ParseTuple(args, "s", &str))
		return NULL;

	// 파이썬 3.4.3에서 tweepy는 utf8 문자열을 던져줍니다.
	// 콘솔에서 출력을 위해 utf-16으로 인코딩을 바꾸어 줍니다.
	wchar_t text[300] = { 0 };
	MultiByteToWideChar(CP_UTF8, 0, str, -1, text, 300);
	wprintf(L"%s\n", text);
	Py_RETURN_NONE;
}

// 파이썬에서 호출할 함수를 가지고 있는 변수.
// 자세한 내용은 Python API 문서를 참고해주시기 바랍니다.
static PyMethodDef OnStatusMethods[] = {
	{ "on_status", (PyCFunction)OnStatus, METH_VARARGS,
	"userstream callback function." },
	{ NULL, NULL, 0, NULL }
};

// 모듈 변수.. 자세한 내용은 역시 Python API.
static struct PyModuleDef OnStatusModules = {
	PyModuleDef_HEAD_INIT,
	"twit_c",
	"userstream callback test.",
	-1, OnStatusMethods
};

// 모듈을 등록할 때 함수포인터를 받네요... 그래서 만들었습니다.
static PyObject* PyInit_Twit(void)
{
	return PyModule_Create(&OnStatusModules);
}

와 이거 점점 길어지는데요 ㅋㅋ....

본격적으로 들어가기전에 boost python에 잠시 이야기하겠습니다.
본래 python을 순수 Python API만을 이용하여 C에서 사용하려고 하면 PyObject*에 대해 수동으로 레퍼런스 카운팅을 해줘야합니다.
무슨 소리냐구요? 메모리를 개발자가 수동으로 관리해 주어야 한다는 소리죠!

아 이 얼마나 귀찮은 이야기입니까... 코드 적게 짜겠다고 임베디드 파이썬 써보려고 하는데 레퍼런스 카운트가 수동이라니;
코드가 더러워질거 같습니다. C++11에서 표준으로 포함된 shared_ptr 같은 놈을 쓰면 인생 참 행복해 질텐데 말이죠.

boost python을 사용하면 그게 됩니다. 넹. 여러분이 레퍼런스 카운팅을 신경 쓸 필요가 없다는거죠. 이 외에 더 좋은 기능도 많습니다. 제가 저 위에 파이썬에서 C 함수 호출하겠다고 장황하게 쓴 저것도 boost python을 사용하면 사실 간단하게 만들 수 있는것 같더라구요. 해보진 않아서 모르겠습니다. 아닌가..?

자세한 내용은 글 시작하기 전에 써놓은 클리앙 링크를 참고해주세요. 이 글보다 몇 백배 친절한 설명이 있으며 이 글보다 예외 처리도 잘 되어있습니다...
자 그럼 이 좋은 부스트 파이썬을 바로 써보도록 하져.

boost::python::object tmp; 를 입력하고 저장하느 순간 Visual Studio의 Intellisense가 비명을 지르기 시작하는게 함정

int main(int argc, char *argv[])
{
	using namespace boost::python;

	// Windows 콘솔에서 유니코드 글자 출력을 위해 필요합니다!
	_setmode(_fileno(stdout), _O_U16TEXT);

	// 이 함수는 Py_Initialize 함수가 호출되기 전에 호출되어야 합니다.
	PyImport_AppendInittab("twit_c", &PyInit_Twit);

	// 파이썬 초기화
	Py_Initialize();

	// 항상 예외는 체크해 주셔야 합니다.
	// 아래 코드를 블럭으로 묶은 이유는 스코프 범위 때문입니다.
	// Py_Finalize 함수가 호출 되기 전에 모든 PyObject*가 해제되어야 합니다.
	// 만약 스코프를 제한하지 않으면 Py_Finalize  이후에 boost::python::object 클래스의 소멸자가 호출되며
	// 이는 런타임 에러로 이어집니다.
	//try
	{
		// scripts 폴더의 twit.py를 import 한 후 그 객체를 twit에 저장합니다.
		boost::python::object twit = boost::python::object(boost::python::handle<>(PyImport_ImportModule("scripts.twit")));

		// twit에서 home_timeline 어트리뷰트를 찾고 호출합니다.
		// boost python에서 어트리뷰트를 찾는 함수는 attr이며, 이 함수의 결과로 리턴되는 object 클래스는 ()가 오버로딩 되어있습니다.
		// 이 오버로드된 ()을 호출하게 되면 해당 어트리뷰트의 함수를 실행합니다.
		// 만약 존재하지 않는 어트리뷰트를 실행하게 될 경우 예외를 내뿜게 되니 예외처리를 해주셔야합니다.
		auto hometimeline = twit.attr("home_timeline")();
		auto hometimeline_sizeof = hometimeline.attr("__len__")();

		// extract함수는 리턴 결과를 특정 타입으로 캐스팅 시켜줍니다. __len__ 함수는 int형을 리턴하므로 int로 캐스팅해줍니다.
		// 만약 캐스팅에 실패하면 (리스트를 int형으로 바꾼다던가... 불가능한 캐스팅의 경우) 역시 예외가 발생합니다.
		int testCount = boost::python::extract<int>(hometimeline_sizeof);
		wprintf(L"%d\n", testCount);

		// 프로퍼티나 변수의 경우 아래처럼 그냥 어트리뷰트만 가져온 후 extract로 타입 변환 해주면 됩니다.
		// 오버로딩된 ()를 호출할 경우 예외가 발생하니 주의해주세요.
		long long maxId = boost::python::extract<long long>(hometimeline.attr("max_id"));

		for (int i = 0; i < testCount; i++)
		{
			auto py_auther_name = hometimeline[i].attr("author").attr("name");
			auto py_auther_screen_name = hometimeline[i].attr("author").attr("screen_name");
			auto py_text = hometimeline[i].attr("text");

			char* utf8_auther_name = boost::python::extract<char*>(py_auther_name);
			char* utf8_screen_name = boost::python::extract<char*>(py_auther_screen_name);
			char* utf8_text = boost::python::extract<char*>(py_text);

			wchar_t u_auther_name[300] = { 0 };
			wchar_t u_screen_name[300] = { 0 };
			wchar_t u_text[300] = { 0 };

			// 변경하는 이유는 위에 파이썬에서 호출되는 함수에 써놓았습니다.
			MultiByteToWideChar(CP_UTF8, 0, utf8_auther_name, -1, u_auther_name, 300);
			MultiByteToWideChar(CP_UTF8, 0, utf8_screen_name, -1, u_screen_name, 300);
			MultiByteToWideChar(CP_UTF8, 0, utf8_text, -1, u_text, 300);

			// wprintf(L"auther id : %s, screen_name : %s\n", u_auther_name, u_screen_name);
			wprintf(L"text : %s\n", u_text);
		}

		wprintf(L"Start UserStream!\n");
		// userstream 서버에 접속합니다!
		// 이 함수의 인자는 async 여부이므로 true을 던져줬으니 비동기로 처리한다는 이야기겠죠?
		// 안타깝지만 작동하지 않습니다. 정확힌 함수가 호출되고 실행되다 일시정지됩니다.
		twit.attr("userstream_start")(true);

		// 사용자로 부터 입력을 받습니다.
		while (1)
		{
			wchar_t input[300] = { 0 };
			std::wcin >> input;
			if (input[0] == L'q')
			{
				twit.attr("userstream_end")();
				break;
			}
		}
	}
	// catch (...)
	// {
	// }

	Py_Finalize();
	return 0;
}

머 홈 타임라인은 얻어오네요 ㅎㅎ. cin까지도 정상적으로 호출됩니다.



하지만 안타깝게도.. 유저스트림이 작동하질 않아요...
파이썬 코드에서 생성한 쓰레드가 중간에 정지되기 때문입니다. ㅜ_ㅜ
해결방법이 있으니까 글을 썼겠져?

이를 위해선 C코드에서 파이썬 쓰레드를 관리해주어야 할 필요가 있습니다.

자 그럼 관리해보죠.

int main(int argc, char *argv[])
{
	using namespace boost::python;

	_setmode(_fileno(stdout), _O_U16TEXT);

	PyImport_AppendInittab("twit_c", &PyInit_Twit);

	Py_Initialize();

	// !추가!
	PyEval_InitThreads();

	{
		boost::python::object twit = boost::python::object(boost::python::handle<>(PyImport_ImportModule("scripts.twit")));
		
		// 현재 파이썬 쓰레드 상태를 가져옵니다.
		auto mainThreadState = PyThreadState_Get();

		wprintf(L"Start UserStream!\n");
		twit.attr("userstream_start")(true);

		// 현재 쓰레드 상태를 저장하고 쓰레드의 global interpreter lock(GIL)을 해제합니다.
		// GIL에 대한 설명은 Python API를 참고해주세요.
		mainThreadState = PyEval_SaveThread();

		while (1)
		{
			wchar_t input[300] = { 0 };
			std::wcin >> input;
			if (input[0] == L'q')
			{
				// 마지막으로 저장된 쓰레드 상태를 가져온 후 GIL을 설정합니다.
				// PyEval_SaveThread()를 호출한 이후에는 파이썬의 현재 쓰레드 상태가 NULL이 되기 때문에
				// 반드시 아래 함수나 PyThreadState_Swap 함수를 이용하여 쓰레드 상태를 변경해주어야 다른 파이썬 코드를 실행 할 수 있습니다.
				// 또한 PyEval_SaveThread() 후 파이썬 코드를 실행하기 전에 GIL을 반드시 설정해야 한다는 사실도 잊지 마시길 바랍니다.
				PyEval_RestoreThread(mainThreadState);
				twit.attr("userstream_end")();
				// 현재 쓰레드 상태를 저장하고 GIL을 해제합니다.
				mainThreadState = PyEval_SaveThread();
				break;
			}
		}
		// 마지막으로 저장된 쓰레드 상태를 가져온 후 GIL을 설정합니다.
		// Py_Finalize 함수 호출 전에 반드시 GIL이 설정되어있어야 합니다.
		PyEval_RestoreThread(mainThreadState);
	}
	Py_Finalize();
	return 0;
}

우와..이렇게 하면 작동해야 할텐데 말이죠...



머져 이 귀찮은건...

tweepy의 코드를 수정해줍니다. tweepy/streaming.py를 편집기로 열고 ReadBuffer 클래스의 161번 줄, 171번줄을 수정합니다.

    def read_len(self, length):
            # ...중간 코드 생략...
            # 아래 코드의 맨 끝에 .decode('utf-8')을 붙입니다.
            self._buffer += self._stream.read(read_len).decode('utf-8')

    def read_line(self, sep='\n'):
            # ...중간 코드 생략...
            # 아래 코드의 맨 끝에 .decode('utf-8')을 붙입니다.
            self._buffer += self._stream.read(self._chunk_size).decode('utf-8')

이렇게 오랜만의 글이 끝났네요. 설명이 무진장 대충이라 이해가 안되시는 부분도 많을거라 생각됩니다....
그런 부분에 대해서는 위에 언급한 사이트를 참고하셔서(...) 위기를 잘 헤쳐나가시길 바랍니다.
사실 이 글과 저 위에 있는 글 2개면 왠만한 임베디드 파이썬에서 문제가 생길것 같진 않네요.

블로그를 방문해 주셔서 감사합니다. 좋은 하루 되시고 즐거운 코딩하시길 바랍니다!


안녕하세요(슬슬 MMD로 뭔가 개발하기 귀찮아지고 있는)지나가던 노인네입니다.
넥슨 공채 쓰다가 자소서에서 막혀서 으으 뭐하지 하고 있는데 제출시간이 지나버렸네요. 똥망입니다. ㅜㅅㅜ...

에.. 그래서 VMD 파서를 일단 들고왔습니다.
PMX 파서는 http://youtil.wo.tc/120 <- 이쪽으로.

라이선스는 역시 MPL v1.1입니다.

https://tmp.devbreak.info:24930/hayandev/VMDParser


본 이름이 깨지는건 일본어라 그래요. 폰트가 없습니다 ㅜ_ㅜ..


참고한 사이트
http://blog.goo.ne.jp/torisu_tetosuki/e/bc9f1c4d597341b394bd02b64597499d
http://harigane.at.webry.info/201103/article_1.html

으음. 참고로 저는 DirectXMath.h(Windows 8 or higher)를 사용합니다..'만'
옛날에 책 따라서 만든 Box 예제가 있길래 그 위에다가 대충 테스트 할려고 구현한거라 XNAMath를 사용하게 됐습니다 ㅇㅂㅇ;

XNAMath(혹은 DirectXMath.h를 include 한 후 using namespace DirectX를 타이핑 했다면)와 Bullet Physics를 동시에 사용하게 되면 C2084에러를 보게 되는데 이 때는 당황하지 마시고

#define BT_NO_SIMD_OPERATOR_OVERLOADS
#include <btBulletDynamicsCommon.h>

이렇게 처리해 주시면됩니다.

자 시작해볼까요?

먼저 엔진을 사용할 준비를 해야겠죠? Bullet Physics Hello World예제는 한번 보고 오셨을거라 생각합니다.
아직 안보신 분은 Hello World! <- 클릭.

btBroadphaseInterface* broadphase = new btDbvtBroadphase;
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration;

btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);

// 제가 테스트 해볼 박스 위치가 (-1,-1,-1), (1,1,1)입니다. 그러니 아래처럼 해줘야죠.
btCollisionShape* boxCollisionShape = new btBoxShape(btVector3(1, 1, 1));
// 변환따위 하지 않습니다. 카메라는 돌아가지만 변환은 하지 않습니다.
btDefaultMotionState* motionStatenew btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, btVector3(0, 0, 0)));

btRigidBody::btRigidBodyConstructionInfo rigidBodyCI(0, bulletUtil.motionState, bulletUtil.boxCollisionShape, btVector3(0, 0, 0))
btRigidBody* rigidBody =  new btRigidBody(rigidBodyCI);
dynamicsWord->addRigidBody(rigidBody);

이제 여러분의 DirectX 프레임워크에 있는 마우스 이벤트 처리 함수에 다음과 같이 코딩해봅니다.


void PickingTest::OnMouseDown(WPARAM btnState, int x, int y)
{
	XMMATRIX proj = XMLoadFloat4x4(&m_Proj); // Projection 행렬입니다.
	XMMATRIX view = XMLoadFloat4x4(&m_View); // View 행렬입니다.

	XMVECTOR rayOrigin = XMLoadFloat4(&XMFLOAT4(0, 0, 0, 1.0f));
	// 혹시 Origin 점 위치가 왜 원점인지 이해가 안가시나요?
	//      /                 |                                             |
	//    /                   |                                             |
	//  /                     |                                             |
	//뷰----광선---NEAR--------->물체                  FAR
	//  \                    |                                             |
	//     \                 |                                             |
	//        \              |                                             |
	//
	// 즉, 광선의 시작점은 카메라 공간(View)에서 카메라 위치인 0,0,0 에서 시작됩니다.
	//
	XMVECTOR rayDirection = XMLoadFloat4(&XMFLOAT4(
		((float)(2.0f * x) / (float)mScreenViewport.Width - 1.0f) / m_Proj._11,
		((float)(-2.0f * y) / (float)mScreenViewport.Height + 1.0f) / m_Proj._22,
		1.0f,
		0.0f));
	// 광선의 시작지점이 원점이므로, 이 원점에서 어떤 "방향"으로 광선이 발사될지 정해줘야합니다.
	// 광선의 방향은 Point(x, y, z) - O(0, 0, 0) = Point(x, y, z)이므로 
	// 저희는 스크린 포인트의 마우스 위치를 뷰 행렬까지 옮겨주면 될 듯 합니다.
	// 원래 Loacl * World * View * Proj * ViewPort 순서대로 곱한 결과가 최종 스크린 좌표이므로(마우스는 이미 최종 스크린 좌표입니다.)
	// Local * World * View = ScreenPos * ViewPort^-1 * Proj^-1 이겠네요.

	XMVECTOR det2 = XMMatrixDeterminant(view); // 만약 DirectXMath 가 아니라 XNAMath 를 사용하신다면 반드시 필요합니다.
	XMMATRIX invView = XMMatrixInverse(&det2, view); // XNAMath XMMatrixInverse는 첫번째 인자가 nullptr 일 수 없습니다. DirectXMath는 nullptr 가능.

	rayOrigin = XMVector4Transform(rayOrigin, invView);
	rayDirection = XMVector4Transform(rayDirection, invView);

	rayDirection = XMVector4Normalize(rayDirection) * 1000.0f;
	// 방향을 구한다음에 길이를 재설정해줍니다. 으음. 길이를 설정해줘야 하더군요. 노말라이즈 안하고 그냥 값 넣어도 작동은 됩니다.

	XMFLOAT3 rayOriginF;
	XMFLOAT3 rayDirectionF;

	XMStoreFloat3(&rayOriginF, rayOrigin);
	XMStoreFloat3(&rayDirectionF, rayDirection);

	// 아래 코드는 이제 보시면 이해 할 것입니다.
	// 자세한 함수 사용 방법은 Bullet Physics Document를 참고해주세요.
	btCollisionWorld::ClosestRayResultCallback rayCallback(
		btVector3(rayOriginF.x, rayOriginF.y, rayOriginF.z),
		btVector3(rayDirectionF.x, rayDirectionF.y, rayDirectionF.z));
	bulletUtil.dynamicsWorld->rayTest(
		btVector3(rayOriginF.x, rayOriginF.y, rayOriginF.z),
		btVector3(rayDirectionF.x, rayDirectionF.y, rayDirectionF.z),
		rayCallback);

	if (rayCallback.hasHit())
	{
		SetWindowText(mhMainWnd, L"성공");
	}
	else
	{
		SetWindowText(mhMainWnd, L"실패");
	}
}

아래는 결과. 마우스를 캡쳐 할 수 있는 캡쳐 프로그램이 없어서 그냥 그림판으로 대충 동그라미 그렸습니다. 동그라미 가운데 부분이 클릭지점 입니다.




고딩 수학을 말아먹은 관계로 뭔가 틀린 부분이 있다면 알려주시면 감사하겠습니다.



왼쪽 - MASS 적용, 오른쪽 - MSAA 미적용
#근대_왜_미쿠댄이_더_자연스럽고_퀄리티가_높은거냐

아직 스피어(PMX 포맷 설명에서는 그냥 일본어로 スフィア 라고 나와있습니다. 이펙트 셰이더 소스코드를 살펴보면 Sphia라고 나와있는데 Sphere인지 뭔지 모르겠네요)를 어떻게 처리해야 하는지 감이 안잡힙니다.
추측으로는 미쿠댄 방식대로 dist ratate_x_y_z의 값을 일일이 하나하나 저장해놓고 이걸 토대로 카메라를 구현한 후, 회전행렬을 만들어 VertexNormal * matRotate 형식으로 곱해주는게 아닌가 라는게 현재 추측 입니다.
하지만
카메라 구현하기 귀찮아 -ㅅ-; (일해라)

매직넘버 노가다와 MikuMikuDance를 잠시 뜯어(...) 이펙트 셰이더를 추출해서 대충 어떻게 렌더링 하는지는 보았습니다.
어음.. 뭐하는 코드인지 이해가 불가능합니다. 일단 따라 타이핑 해본 결과가 위 사진이긴 합니다.(그런데 베껴써도 결과가 똑같이 안나와!? 어떻게 된거야!?)
코드를 보면 아시겠지만 캐릭터 텍스쳐 샘플러를 s1 이라고 이름 지어놓고 주석으로 샘플러 라고 써놓은 센스를 보아 아마도 누군가 봐도 분석하기 어렵게를 노린게 아닌가 싶으니(그렇게 믿고싶습니다.)
코드를 암호화 안해준 것 만으로 감사히 여겨야겠습니다.

일단 위 사진은 대충 PCF 기법을 사용한 그림자 맵과 대충 VIew행렬 때려넣은 스피어와 게임 개발 포에버에 나온 글을 참고하여 외곽선을 구현한 결과입니다. 제 실력의 혼자 덩그러니 있는 무쓸모 소립자 수준이라 이거 만드는데만 한달 걸렸네요 -ㅅ-;;

시작은 엔하위키에 미쿠댄이 사실상 오픈소스랑 다름 없다 라는 내용을 보고 어라 그래? 하고 개발 시작한건데 오픈소스는 개뿔때기나 정보가 드럽게 없어서 개노가다짓을 하고 있습니다.
뭐 그나마 최신버전의 미쿠댄에서 셰이더 파일을 뜯어네 카툰렌더링을 어떤 방식으로 하는지 알아낸것 만으로도 감격의 눈물이...
사실 이 셰이더 뜯어내는 방법은 안쓰려고 했던 방법인데 2주 동안 아무리 값을 바꾸고 방식을 바꿔봐도 똑같이 안나오길래 결국 써버렸습니다..

이거 만들면서 느낀게 카툰 렌더링 만드는 방법은 참 많구나.. 하나와 만들기 진짜 귀찮구나... 정도..

만들면서 멘붕했던건

1. 카툰 렌더링 방법. 셰이더 뜯고 어느정도는 해결 됨.
2. Toon 텍스쳐는 대체 뭐냐... 현재 추측으로는 float2 ToonUV = float2(그림자_맵_에서_얻어온_깊이_값, 텍스쳐UV.v) <-인데 테스트는 아직입니다.
3. 스피어.. 이것도 추측일 뿐...
4. 추가 UV가 뭔지 모르겠음.
5. 모프가 뭔지 모르겠음.
6. 캐릭터 애니메이션도 문제라면 문제인데 BDEF라던가 뭐 이런 본 방식들이 뭘 말하는건지 전혀 모르겠음ㅜ_ㅜ..
7. 애초에 미쿠댄스 사용 방법도 모릅니다(사용해 본 적이 있어야지...)
8. 한자를 읽을 줄 모름... 일본에서 만든거다 보니 Wiki라던가 대부분의 글이 일본어라 한문이 들어가 있는데 번역기의 한계 때문에 읽기가 힘듭니다..

그럼 저는 이제... 아마도 애니메이션을 구현하러 가야겠군요... 으음.. 다..다녀오겠습니다.
좀 더 자세한 구현 이야기는 다음 멘붕 시간에뵈여!

소스코드 https://tmp.devbreak.info:24930/hayandev/PMX_Parser

라이선스는 MPL v1.1 입니다.


테스트 프로그램 실행 결과.

몇몇 코드가 상당히 지저분한데 아직 개발이 끝난건 아닙니다. 조만간 v2.1 도 지원되게 수정할거에요.
UTF8은 지원 할 생각 없습니다. 누가 포크해서 만들어주시면 감사하겠습니다.

안녕하세요! 2시간 쯤 전에 개발을 마치고 소드 아트 온라인 프로그레시브 2권을 읽고오니 공돌이가 싫어하는 태양은 사라지고 기분 좋은 밤 공기가 맞이해주는군요.
아 정말 좋은 밤입니다. 맥주 마시고 싶네요 ㅇㅅ;;

이 글은 이걸로 마지막입니다.

아직 개발해야 할게 몇 개 남았지만 제 실력에는 좀 무리일거 같고 여러분 실력이면 충분히 개발 가능하지 않을까 싶습니다.
이미 개발하시고 'ㅋㅋㅋ 이거 글 언제 올라오냐 ㅋㅋ 포기한건가 ㅋㅋㅋ' 라는 생각을 하고 계신 분들도 계시겠죠 ㅜ_ㅜ 제가 머리가 나빠서 그렇습니다 ㅜㅜ

뭐 기분 좋게 스크린샷으로 시작해보죠. 다운그레이드 됐다면 다운그레이드 됐지만 설치된 글꼴만 사용 가능합니다.



오오옹. 잘 되는 거십니다.

이 글은 http://youtil.wo.tc/113 <- 에서 설명하는 글을 마친 상태에서 진행됩니다.

먼저 EditBox를 띄우는 방법을 알아야겠죠.
이미 알고 계시겠지만 3D에서 흔히 사용하는 투영 행렬은 마지막 계산 결과가 -1, -1, 0 ~ 1, 1, 1 사이에 있게됩니다. 다시 말해서 프로젝션 행렬을 굳이 연산하지 않아도 괜찮다는 말이죠.

그래서 버텍스가 (-1, -1, 0.1), (1, 1, 0.1)인 사각형을 렌더링 하려면 그냥 identity 행렬을 곱해주고 그대로 렌더링하면 되고 그 결과는 Client Window의 0,0 부터 Width, Height까지 완벽하게 꽉 찬 사각형이 렌더링 되게 됩니다. 오호, 마침 저희에게 딱 필요하지 않나요? 이렇게 된거 아예 저 버텍스에다가 Scaling하고 Translation을 먹여서 크기를 바꿔서 클라이언트 좌표에 맵핑시켜서 렌더링 하는것도 괜찮은 방법인거 같습니다. 오오. 속도는 모르겠지만요.....

먼저 클라이언트 좌표계를 NDC공간으로 바꿔보죠. 으음. 일단 Client Window의 Width와 Height가 필요할것 같군요.

struct TransformNDC
{
	float scaleX, scaleY, scaleZ;
	float movX, movY, movZ;
};

TransformNDC ClientToNDC(const RECT& target, int windowWidth, int windowHeight)
{
	float width = target.right - target.left;
	float height = target.bottom - target.top;

	float scaleX = width / windowWidth;
	float scaleY = height / windowHeight;
	float scaleZ = 1;

	float ratioX = 2.0f / windowWidth;
	float ratioY = 2.0f / windowHeight;

	float movX = -(1 - scaleX) + (ratioX * target.left);
	float movY = (1 - scaleY) - (ratioY * target.top);
	float movZ = 0;

	return TransformNDC{ scaleX, scaleY, scaleZ, movX, movY, movZ };
}

굳이 클래스에 넣어야 하나 싶어서 그냥 만들어봤습니다.
대충 이렇게 씁니다.

auto translate = ClientToNDC(RECT{ 0, 0, 800, 600 }, mClientWidth, mClientHeight);

XMMATRIX scale = XMMatrixScaling(translate.scaleX, translate.scaleY, translate.scaleZ);
XMMATRIX world = XMMatrixTranslation(translate.movX, translate.movY, 0);

XMMATRIX를 블로그에서 쓰려니 갑자기 생각난건데 개발 할 때 DX초기화 하는걸 직접 하기가 좀 뭐시기 해서 제가 보는 책에 있는 프레임워크를 가져다 썼더니 쓰게됐네요. 전에 넥슨 다니시는(부럽) 학교 선배분께 'XNA라이브러리 쓰나요?' 라고 물었다가 '그게 망한지가 언젠데 아직도 쓰고있어?'라는 소리를 들어서(...) 저도 슬슬 버릴까 합니다만 책을 다 볼 때 까지는 쓰려고 합니다 ㅇㅅ.

뭐 이제
http://youtil.wo.tc/114 <- 이 글을 참고하셔서 IMM 컨텍스트를 적당한 메시지에 생성해 주시고...
http://youtil.wo.tc/115 <- 를 참고해서 적당히 insert와 erase '만' 쓰도록 하죠.
사실 115번 글에서 사용한걸 사용 가능할거라고 믿었는데 산산히 부숴졌습니다. 뭐 그래도 DirectWrite의 DrawText도 잘 만들어져 있어서요. 위 글에서 사용한 방법보단 좋을것같네요. 다소 귀찮지만.

적당히 첫번째 글을 마쳤다면 DWriteFactory(IDWriteFactory)객체가 하나 있을거라고 생각됩니다.(사실 저는 여기저기 짜집기한거라 DWriteFactroy가 첫번째 글에 있는 그 링크에 있는지 없는지 기억이 안나네요; 없으면 만들어주세요 ㅜ_ㅜ)

그래서 DWriteFactory를 잘 살펴보면 CreateTextLayout라는 맴버 함수가 있습니다. 이 녀석을 이용해 IDWriteTextLayout이라는 녀석을 만들어보죠.

IDWriteTextLayout* layout;
// DWriteFactory가 없다면 만들어주세요.
DWriteFactory->CreateTextLayout(printText.c_str(), cursorPos, TextFormat, mClientWidth, mClientHeight, &layout);

그러고 나서 layout 인터페이스에 짝대기를 하나 그어주니... GetMetrics 라는 맴버 함수가 하나 나오는군요!!! 우오오! 그리고 그 밑에 좀 더 찾아보니 무려 GetLineMetrics라는 녀석도 있습니다 우와아아! 이걸로 멀티 라인을 만들 수 있어요! 안에 무슨 변수가 들어가는지는 MSDN을 참고하시고! 여기 서중요한건 GetMetrics로 현재 몇 라인을 가지고 있냐와 폰트의 Height를 구할 수 있습니다. 그리고 GetLineMetrics라는 녀석을 사용하면 각 라인에 몇개의 글자가 들어가있는지 알 수 있죠.
자 그럼 아래 발로 짠 코드를 잠시 발로 볼까요?

IDWriteTextLayout* layout;
// 출력할 때는 모든 문자열이 필요하지만 길이를 계산할 때에는 현재 커서 위치만 알면 됩니다.
// 아래 두번째 인자를 봐주세요. 현재 커서 위치를 담고있는 변수입니다.
// mClient* 가 들어가는 곳은 에디트 박스의 크기를 넣으시면 됩니다. (만들어질 에디트 박스의 크기가 아닙니다. 보여지는 에디트 박스의 크기입니다!)
// 아래에서 사용되는 mClient*도 같은 의미입니다.
DWriteFactory->CreateTextLayout(printText.c_str(), cursorPos, TextFormat, mClientWidth, mClientHeight, &layout);

DWRITE_TEXT_METRICS metrics;
layout->GetMetrics(&metrics);

std::vector<DWRITE_LINE_METRICS> lineMetrics(metrics.lineCount);
UINT32 lineCount;

layout->GetLineMetrics(&lineMetrics[0], lineMetrics.capacity(), &lineCount);
ReleaseCOM(layout);

int length = lineMetrics[lineCount - 1].length;
// 전체 string에서 현재 라인의 string만 구하는 코드입니다.
// 현재 라인에서 커서를 움직이기 위해서는 현재 라인의 글자가 렌더링될 width를 알아야 하거든요.
const wchar_t* currentLineText = printText.c_str();
// 아래 배열이 음수가 될 일은 없냐구요? 없습니다. length의 최대 길이는 cursorPos와 같거든요.
currentLineText = ¤tLineText[cursorPos - length];
DWriteFactory->CreateTextLayout(currentLineText, length, TextFormat, mClientWidth, mClientHeight, &layout);

layout->GetMetrics(&metrics);
ReleaseCOM(layout);

자 이제RECT를 계산해서 위에서 만든 ClientToNDC 함수를 사용해서 사각형을 잘 변환 한 뒤 렌더링 하면 커서가 되겠죠?

// 블로그로 코드를 옮기다 보니 함수의 일부분만 올리게 되서
// 구조가 바뀌다보니 RECT변수를 급하게 하나 추가했습니다. 그래서 초기화 안했습니다.
// 실제 제가 사용하는 코드에서는 사용자 정의 구조체나 클래스는 제대로 참조자로 받아서 수정하지 return 따위도 하지 않습니다.
RECT rect;

rect.top = (lineCount - 1) * ceil(lineMetrics[0].height);
rect.bottom = rect->top + ceil(lineMetrics[0].height);
rect.left = ceil(metrics.widthIncludingTrailingWhitespace);
rect.right = rect->left + 1;

return rect;

드디어 제가 블로그를 시작한 이래로 가장 가장 길게 쓴 글이 막을 내렸군요.
너무 대충대충 설명하는 느낌이라 정말 죄송합니다. 이번 글은 1편 빼면나름 성실히 작성한다고 작성한건데 생각처럼 잘 되진 않았네요.

부디 제 글이 조금이나마 도움이 되길 바랍니다.
저는 이제 월광 1권을 읽어보러~~~ (저를 덕후로 만든게 제이노블인지라 제이노블을 좋아합니다.)

... 멋지게 실패했습니다.

아니 사실 실패라기보단..거의 포기에 가깝네요. 여러가지 한계가 너무 많이보여서요.
해결 방법도 찾았고 나름 간단한 방법으로 해결이 가능하다는 사실도 알고있지만.. 의욕이 너무 떨어지네요.

뭐 일단 만들어 볼 생각이긴 합니다.
그러나 폰트를 설치 안하고 직접 읽어들여서 사용하는건 좀 귀찮은 일이 되버려서(...) 아마 폰트를 직접 읽어들이는 방식은 아닐것 같습니다.

일단 폰트를 직접 로드하는 방식에 약간의 문제가 발생했습니다.
마지막 글자가 ' '<- 공백 문자라면 이것은 길이로 치지 않는다는 것. 그래서 커서를 옮길 때 마지막 문자가 공백일 경우 커서가 안옮겨집니다...
제가 원하는건 그런게 아니므로... 일단 이 방법은 잠시 포기해야 할 것 같습니다.

그렇다고 폰트를 읽어들여서 하는 방법이 없냐 라고 묻는다면 있습니다.
Microsoft Windows SDK 7 Sample 소스코드에 이 해결책이 있습니다. 바로 커스텀 폰트 컬렉션 인터페이스를 상속받아 이 인터페이스를 직접 구현해야하는데
구현해야할 인터페이스가 3개가 있습니다.

안타깝게도 제가 IUnknown에 대해 몰라서(쩝...) 소스코들를 봐도 이게 대체 뭐하는건지 알 수가 없네요.

두 번째 문제는 커서의 위치가 일정하지 않다는 것 입니다.
영문의 폰트에 보면 Metrics에 descent 와 ascent라는게 있는데 이 문제 때문에 Drawing 영역의 Top Bottom 위치가 일정하게 잡히지 않습니다.
그래서 글자를 입력할 때 마다 커서의 위치가 바뀌는 문제가 발생합니다 ㅜ_ㅜ...(f와 j... 이 두개의 글자가 제일 말썽이더군요..)


사진을 모시면 아시겠지만 마지막 j를 타이핑 한 후 커서가 진짜 조금 내려간 모습이 보입니다.

일단 이 방법도 사용은 가능하지만, 마지막 띄어쓰기가 보여지지 않는다는게 너무 찝찝해서(...)
일단 이 방법 말고 이미 설치된 폰트를 이용해서 출력하는 방법을 이용해 보려구요.
MS Sample 프로젝트 라이선스도 한번 확인해 본 다음에 그대로 써도 된다면 그 방식도 한번 써 보려고 합니다.

조금 시간이 더 걸리겠군요 ㅇㅅㅇ.; 일단 애니나 라노베좀 보면서 멘탈좀 정화하다가 다시 시도하겠습니다.

혹시 참고할 만한 자료가 있다면 알려주시면 감사하겠습니다.

참고 사이트

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNo=20&no=687984&ref=687984
http://support.microsoft.com/kb/74299
http://msdn.microsoft.com/en-us/library/windows/desktop/ms533820%28v=vs.85%29.aspx
http://msdn.microsoft.com/ko-kr/library/y505zzfw%28v=vs.110%29.aspx
http://msdn.microsoft.com/ko-kr/library/xwf9s90b%28v=vs.110%29.aspx
http://cx5software.sakura.ne.jp/blog/2011/01/18/directwrite-metrics/
http://msdn.microsoft.com/en-us/library/windows/desktop/dd941785%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd941710%28v=vs.85%29.aspx
http://msdn.microsoft.com/ko-kr/library/windows/desktop/dd756583%28v=vs.85%29.aspx
http://www.gamedev.net/topic/309327-direct3dfont-using-local-folder-ttf/

기타 등등.

+ Recent posts