삽질하는플머

책장 넘기는 효과 구현하기 - 3.

여가생활/산수공부

1편 - http://oranke.tistory.com/119

2편 - http://oranke.tistory.com/120


어디 제약될 필요 없는 블로그질이라고 해도... 시작한 일은 끝을 봐야 하는 법인데 너무 오래 끌었다. 

지난번 글 이후 초딩이던 큰아들놈은 중학생이 되었고, 신생아였던 둘째가 초딩이 되었으니 세월이 많이 흐르기는 했다. 

블로그 검색로그에 간간히 남아있는 "책장 넘기기 효과"를 보며 어떤 형태로건 마무리를 지어야지 라고 생각하였다. 

(사실... 하드 정리하다가 짱박혀있던 소스를 발견한 이유가 크지만...)


기억을 되살리기 위해 이전 강좌에서 언급했던 몇가지 변수를 되짚어보자. 


  • MvTL - 상단 모서리 이동점
  • MvBL - 하단 모서리 이동점
  • 접힘점1 - 책장이 접힌 위쪽 좌표
  • 접힘점2 - 책장이 접힌 아래쪽 좌표


MvTL값을 임의 위치에 놓고 다른 점들을 비례식으로 계산할 때, MvTL 위치를 적절히 제한해주어야 한다는 것이 지난 글의 마지막이었다. 

그렇다면 어떤 녀석을 구속조건으로 삼으면 좋을까. 


페이지가 이동할 때 접힘점 1과 2, 그리고 이동점간의 관계를 가만히 생각해보자. 


접힘점1을 적당한 위치에 고정하고 이동점 MvTL을 이리저리 움직여보면, 접힘점 2는 책장의 수직부분과 하단부분 내에서 위치하게 된다. 책장이 찢어지지 않는 이상 이 범위를 벗어나지 않게 되므로 0~1 사이의 비율값으로 나타낼 수 있다. 상단, 하단, 수직부분의 비율값을 각각 조절비율 rT, rB, rM 이라고 부르자. 


기준점 MvTL이 주어질 때의 조절비율을 계산하고, 얻어진 비율값을 0~1 사이로 제한한 뒤 다시 기준점을 계산하면, 책장의 모양이 유지되는 적절한 위치값들을 얻을 수 있다. 조금 복잡하게 들릴지 몰라도 어차피 계산은 컴퓨터가 하는 것이니 일단 시켜보자. 


비율변화를 적절히 제한하고 또 구현을 편하게 하기 위해 스크롤바 컨트롤을 사용했다. 

폼 위에 ScrollT, ScrollM, ScrollB 세 개의 TScrollBar를 올리고 Min값은 0, Max값은 10000으로 설정한다. 








마우스가 눌린 Y좌표에 따라 페이지 윗부분을 움직일지, 아니면 아래부분을 움직일지 여부를 결정하기 위해 적절한 타입을 정의하자. 


type

  TRateCalcType = (rctTop, rctBottom);


var
  RateCalcType: TRateCalcType;




이 값은 폼의 MouseDown 이벤트에서 할당한다. 


procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;

  Shift: TShiftState; X, Y: Integer);

begin

  // 화면의 세로 기준으로 절반을 넘어가면 하단을 기준으로 계산.

  if Y > (ClientHeight / 2) then

    RateCalcType := rctBottom

  else

    RateCalcType := rctTop;

......





비율계산타입 RateCalcType과 페이지의 폭, 너비, 그리고 MvTL값을 기준으로 지난번의 비례식을 사용해 rT, rM, rB를 계산해보자. 


CalcPagePosRate





이렇게 계산된 비율값으로부터 다시 상, 하단 이동점들과 접힘점들을 계산하는 함수는 다음과 같다. 

CalcPageInfo





폼의 마우스 이동에서 계산한 비율을 스크롤바에 던지고, OnChange 이벤트에서 화면을 적절히 구성한 결과물은 다음과 같다. 






소스와 실행파일은 여기에. 터보델파이로 빌드되었다. 


BookTest.7z



앞의 코드에서 언급된 TVector2f 및 벡터연산 함수는 여기 포함된 소스를 참고하자. 

위의 설명이 조금 이상하더라도 마우스와 스크롤바를 이리저리 옮겨보면 글쓴놈의 의도가 금방 이해 될 것이다. 




함께 포함된 BookTest2.exe 는 MvTL 위치선정함수(CalcMoveBasePos)의 테스트코드를 풀고 적절한 댐퍼를 추가한 것이다. 

왠지 그럴듯해 보이지 않는가??? 




-----

마우스 위치에 따라 "division by zero" 예외가 발생합니다. 
이전에 다운받았던 분들은 Unit1.pas 의 말미에 initialization 섹션을 추가하고 다음 코드를 넣어주세요. 

......

initialization
  Set8087CW($133F);

end.



모짜르트 주사위 게임

여가생활/Others



초딩 마지막 학기를 보내던 아들이 학기 과제로 준비하던 주제가 궁금해 물어보니 알레아토릭이란다. 

이 중 모짜르트가 만든 작곡 규칙이 있는데, 미리 작곡해 둔 176+96개의 음절을 주사위를 던져 조합해 3/4박자 춤곡을 만든다나...
조합수도 어마어마해서 모든 경우의 수를 다 들어보려면 팔백억년이 걸린다고 한다. 되게 신기했음. 


정보가 가장 잘 정리된 사이트는 여기 였다.
각 마디별 미디파일을 내려받을 수는 있지만,  아쉽게도 완성된 음악을 만드는 기능은 동작하지 않았다.

자료조사와 정리는 그렇다고 치고, 발표는 어찌할꺼냐 물으니... 

1. 모든 마디를 악보로 출력하고 주사위를 던져 배치한 뒤 들고 간 악기로 연주한다. 
2. 몇 개의 음악을 예제로 미리 조합해 MP3로 만들어 재생한다. 

등등의 아이다운 아이디어를 내놓았다. 그러면서 조합결과를 합쳐주는 프로그램을 만들어주면 안될까 하며 슬그머니 눈치를 본다.
그래서 "아빠가 정말 심심할 때 만들어줄께~" 하고 약속을 해 버렸다. 아빠는 항상 심심한게 함정...

미디 재생하는 소스는 오래전에 델마당의 솔개님이 공개해주신 멋진 코드가 있어서 살짝 손 봐 사용했다. 


썰렁한 실행 화면... 



컴파일한 실행파일은 아래에. 

Mozart.zip



소스는 깃헙에...  터보델파이 또는 BDS2006 으로 빌드 가능. 


생각해보니 가족을 위해 코딩 해 본 기억은 별로 없는 듯 하다. 앞으로도 몇 번이나 있을지 모르겠고...
아무튼 아들 덕에 재미난 경험을 했고, 발표회도 잘 마쳤고, 어느덧 시간이 흘러 아들은 중딩이 되어버림. 수염도 거뭇거뭇... 


한 줄 요약: 모짜르트 만세!

윈도 소켓의 프록시 DLL 만들기

탐구생활/Delphi

며칠 전 우연히 감동적인 강좌를 하나 읽게 되었다. 

 

http://www.codeproject.com/Articles/16541/Create-your-Proxy-DLLs-automatically 

 

양병규 형님이 "API Hooking 요점정리" 의 13번 항목에서 "단순 무식하고 쉽지만 실용성이 없다"고 소개한 그 기술에 무려 "실용성"을 부여하는 강좌 되시겠다. 

 

 

프록시 DLL을 만들 때 가장 어려운(귀찮은) 점은, DLL의 모든 함수에 대해 호출규약과 인자를 맞춰 원래 함수를 불러주는 일인데, 이 강좌에서는 이런 귀차니즘을 "__declspec(naked) " 라는 지시자를 써서 간단하게 해결하고 있다. 이 지시자는 VC++의 x86에서 지원되며, 호출시점의 스택상태를 그대로 유지하는 기능을 가진단다. (이번에 처음 구경함) 

 

델파이에서 이런 효과를 내려면 어떻게 하나 고민해봤는데, 스택 구조만 유지한다면 대충 이렇게 해도 잘 되는군. 

 

procedure Test();

asm

  jmp [원래주소]

end;

 

 

 

몰라도 되는 녀석들은 이런식으로 처리하고, 실제 가로챌 함수들에 대해서만 신경 쓸 수 있다는 얘기. 

 

 

그럼 이걸 어디에 쓰면 좋을까?? 

저 강좌와 마찬가지로 만만한 윈도 소켓 DLL을 가지고 놀아 보자. 

 

소켓 API를 사용하면 대충 wsock32.dll -> ws2_32.dll -> mswsock.dll 의 흐름으로 관련 라이브러리들이 링크된다. 

wsock32나 ws2_32 의 프록시를 어플리케이션과 동일한 디렉토리에 넣어두고 send() 나 recv() 같은 함수를 가로채면 패킷을 쉽게 감청할 수 있다.

 

 

델파이 코드를 뱉도록 강좌의 소스를 살짝 수정해 wsock32, ws2_32에 대한 프록시 DLL 프로젝트 파일을 만들어 보았다. 

 

Winsock_Proxy_Src.7z
다운로드

 

 

 

wsock32.dpr 의 주요부분은 다음과 같다. 

 

 

library wsock32;

 

uses

  Windows, SysUtils;

 
......
 
 

procedure __DLLProc(Reason: Integer);

begin

  case Reason of

    DLL_PROCESS_ATTACH:

    begin

      hl := LoadLibrary(PChar(GetSysDirectory + '\wsock32.dll'));

      if hl = 0 then Exit;

 

      OrgFuncs.Arr[0] :=  GetProcAddress(hl, 'accept');

      OrgFuncs.Arr[1] :=  GetProcAddress(hl, 'bind');

      ......

      OrgFuncs.Arr[73] :=  GetProcAddress(hl, 'AcceptEx');

......
 

// AcceptEx

procedure __E__73__();
asm
  jmp [OrgFuncs.Base + SIZE_OF_FUNC * 73]
end;
 

......

 

exports

  __E__73__ index 1141 name 'AcceptEx',

  __E__74__ index 1142 name 'GetAcceptExSockaddrs';

 

......

 

 

 

원본 DLL과 동일한 이름의 함수들을 Export 하고, 이 함수가 불릴 때 OrgFuncs 변수에 담아둔 원래 함수들의 주소로 점프시키고 있다. 호출시점의 스택구조를 유지하므로 원본 함수의 호출규약이나 인자값, 리턴값도 신경 쓸 필요가 없다. 

 

이제 실제로 AcceptEx 함수의 동작에 간섭해보자. WinSock 유니트를 열어 이 함수가 어떻게 정의되어있는지 배껴온다. 

 

function AcceptEx(sListenSocket, sAcceptSocket: TSocket;

  lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,

  dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;

  lpOverlapped: POverlapped): BOOL; stdcall;

 
 
 

 __E__73__() 프로시저를 위 내용 그대로 재정의 해 보자. 

 

 

// AcceptEx

function __E__73__(sListenSocket, sAcceptSocket: IntPtr;

  lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,

  dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;

  lpOverlapped: POverlapped): BOOL; stdcall;

type

  TAcceptEx = function (sListenSocket, sAcceptSocket: IntPtr;

    lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,

    dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;

    lpOverlapped: POverlapped): BOOL; stdcall;

begin

  // 파라미터 가지고 노는 곳

  // ..........

 

  // 원래 함수 호출

  Result :=

    TAcceptEx(OrgFuncs.Arr[73])(

      sListenSocket, sAcceptSocket,

      lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,

      dwRemoteAddressLength, lpdwBytesReceived, lpOverlapped

    );

end;

 

 

 

 

호출규약과 인자를 맞추어주고 다시 원래 함수에 그대로 전달하고 있다. TSocket 은 다시 선언하기 귀찮아 원래 타입인 IntPtr 형을 사용했다. 

리턴시 스택을 유지해야 하므로 원래 함수를 호출한 뒤 되도록 아무 일도 하지 말자. 지역변수 선언 또한 금물이니 주의하자. 

 

 


 

2016.06.28 추가. 

 

dumpbin 으로 얻어진 TXT 파일을 줏어담는것은 아무래도 모양이 빠져서... 

독립적으로 실행되는 간단한 유틸리티를 만들어 보았다. 이름하여 "프록시 DLL 생성기"

 

 

 

ProxyDllGen.7z
다운로드

 

 

실행시키고 DLL을 끌어다 던지면 Export 된 함수 목록을 보여준다. 

"Generate" 버튼을 누르면 해당 DLL의 델파이 버전 프록시 코드를 생성해준다. 

 

라자루스로 만들어봤는데, 못 본 사이에 참 좋아졌다. 터보델파이를 대체하기에는 조금 아쉽지만...

 

 


 

2017. 02.16

 

github 에 코드 올려둠. 

https://github.com/oranke/proxy-dll-generator

 

 

요물덩어리 emscripten. 세번째.

탐구생활/WEB 관련
텍스쳐를 부르기 위해 zlib 링크. 
간단할 줄 알았는데... 문서대로 되는 게 거의 없네... 
결국 해법은 "emcmake cmake"...  

아무튼 이제야 뭔가 동작하는 것 처럼 보인다. 

처음에는 단순히 예전에 써먹던 고정파이프 방식을 처리하는 셰이더면 충분하다 싶었는데
가지고 놀다 보니 이거 참 편하고 좋은 물건이었군... 


화면 클릭, 우클릭은 Y축 회전.
W키는 와이어 프레임.
L키는 조명 테스트. 

(가끔씩 github.io 가 반응이 없네...)


 



2017.3.30. 

컴파일결과를 올려놓았던 구글드라이브 호스팅이 작년 언제쯤부터 정지되어 먹통이었는데... 

차일피일 미루다 github.io 로 이동시킴. 


요물덩어리 emscripten. 두번째.

탐구생활/WEB 관련

VBO, IBO 붙이고 행렬스텍과 초간단 조명 구현. 
모델은 뭘 쓸까 하다가... "개발자에게 배우는 게임 개발 테크닉" 에 들어있던 ASE를 변환해 붙여봄. 

네이티브에서는 대충 굴러가는 코드가 emscripten으로 빌드만 하면 까탈스럽게 에러를 토해내는데
그래도 삽질하면 어찌어찌 돌아가기는 하네. 

황당했던 경험에 순위를 매겨보면... 
1. char 세 개로 패킹한 노멀좌표가 끼어있으면 정점 stride가 먹지 않음. 정점 구조체 크기는 4의 배수여야 함. 

2. std::vector 만 쓰면 쏟아지던 에러. library-glfw.js 내부에 공백대신 낑궈져있던 문자를 지워 해결. 

3. 셰이더에 보낸 정점은 셰이더 내에 어떤 형태로든 써먹어주어야 함. 안그러면  아무것도 표시 안됨. 


이제 텍스쳐만 발라주면 그럴듯하겠다. 




화면 클릭, 우클릭은 Y축 회전.
W키는 와이어 프레임.
L키는 조명 테스트.