본문으로 바로가기

파일의 IT 블로그

  1. Home
  2. 보안 강좌/CheatEngine
  3. [Cheat Engine Game] 치트엔진 튜토리얼 게임 Step #3 풀이 [完]

[Cheat Engine Game] 치트엔진 튜토리얼 게임 Step #3 풀이 [完]

· 댓글개 · KRFile

Step 3:
문을 열기 위해 모든 발판을 초록색으로 만드십시오.

주의 : 적들에게 한 방 맞으면 죽습니다. (그리고 안타까운 패자가 됩니다.)

재미있게 즐기세요!

힌트 : 많은 해결 방법이 있습니다. ex) 적들과 충돌 검사 찾기, 텔레포트, 날기 등

 

안녕하세요 파일입니다! 오늘은 치트엔진 튜토리얼 게임의 마지막 문제인 Step 3 입니다.

이번 문제까지 풀면 더 이상 치트엔진 튜토리얼에 관련된 문제는 없는 거 같습니다. 참 아쉽지만 이번 문제 역시 알아가는게 꽤나 많은 문제입니다. 그럼 시작해봅시다!

 

Try

문제 설명을 읽어보셨으면 알다 싶이 이번 문제는 저 빨간 밟판을 캐릭터로 밟아 전부 초록색으로 만들면 되는 문제입니다. 저 밟판을 초록색으로 만들면 저기 오른쪽에 잠긴 문이 열립니다.

 

참고로 내 캐릭터는 아이워너비더가이 시리즈를 떠올리게 하듯 딱 1대 맞으면 죽습니다. (1-hit-kill)

 

또 화면 왼쪽아래의 P를 누르거나 키보드로 P를 누르면 게임을 잠시 정지시킬 수 있습니다.

이 기능은 값을 찾는데 유용하게 사용되니 꼭 알아두세요. 정지 상태에서 P를 다시 누르면 정지가 해제됩니다.

 

지금까지 치트엔진 튜토리얼이나 튜토리얼 게임은 어떻게 하든 치트를 사용하지 않으면 절때 깰 수 없는 문제들 밖에 없었습니다. 그런데 이번 문제는 생각해보면 치트 없이 피지컬로 깰 수 있다는 생각이 듭니다.

 

한대 맞으면 죽는다고 해도 캐릭터로 잘 피해서 저 밟판만 다 켜면 될 거 같거든요.

 

하지만 치트 없이 피지컬로 저 발판을 다 활성화 하는걸 성공하면..?

 

저렇게 적들이 문 앞에 이동해서 클리어를 못하도록 막습니다.

처음 푸신 분들은 스포해서 죄송하지만 삽질을 미리 방지해드린겁니다 ㅋㅋ

 

역시 그냥은 안보내준다!!!

 

어쨌던 위 문제를 풀기 위해 여러가지 방법을 사용해볼 수 있겠습니다만, 이 문제를 푸는 여러 방법을 봤는데 일단 공부 목적으로 좋은건 역시 충돌방지인거 같습니다.

적과 충돌해도 충돌 검사를 무시해서 죽지 않게하는 방법으로 문제를 풀도록 하겠습니다.

그리고 캐릭터 텔레포트도 구현해보고, 무한점프도 구현해보겠습니다!

 

How to? & Remind

 

오늘 솔루션은 순서대로 진행해야 하므로 Solution 이라는 제목 대신 Step이라는 명칭을 사용하도록 하겠습니다.

 

방금전에 말씀드렸듯이 일단 제일 처음에 하는건 플레이어가 적들과 충돌해도 죽지 않게 하는게 먼저입니다. (충돌검사 무효화)

 

이전 튜토리얼 게임 #2번 문제를 복습해보자면 내 캐릭터와 적 캐릭터에 관한 정보는 구조체 형태로 관리되고 있었습니다.

 

플레이어에 관련된 여러 정보는 따로 따로 메모리 상에 아주 먼 곳에 흩어져 있는게 아니라 가까이, 몇 바이트 떨어져 메모리 상에 뭉쳐 있는 구조로 관리되고 있었으며, 이런 구조체를 빨리 파악해야 캐릭터 객체에 관련된 정보를 쉽게 파악할 수 있었습니다.

 

예를 들어 내가 지금 캐릭터에 대해 hp 값 정도밖에 모르는데, 치트엔진 구조체 분석 기능을 활용하면 hp 값 주변 연속된 메모리 공간을 확인하게 됩니다. 이에 따라 캐릭터에 대한 구조체를 파악하고, hp 값 주변에 있는 캐릭터의 공격력, 좌표, 크기 등 여러 부수적인 정보를 한꺼번에 취득할 수 있습니다.

 

이번 문제 역시 캐릭터의 구조체를 파악해서, 이 구조체에 캐릭터가 적과 충돌했는지 저장하는 변수가 있을 것이라고 가정하고 문제풀이를 진행하겠습니다. 그러므로 먼저 캐릭터의 구조체를 파악해야 합니다.

 

Step1 - 구조체 파악

저번 튜토리얼 게임 #2번 에서는 캐릭터의 HP 값을 찾고 이 값을 기준으로 주변에 구조체를 파악했지만, 이번 캐릭터는 한 대 맞으면 죽는 참피기 때문에 체력이 없습니다.

 

그러므로 이번엔 캐릭터의 좌표를 활용해, 이를 주변으로 구조체를 분석하여 캐릭터의 구조체를 찾아보겠습니다.

 

이 게임은 실제로 캐릭터의 좌표를 변수에 기록하고 있습니다. 당연히 게임이 돌아갈려면 컴퓨터가 캐릭터의 위치를 잘 알고 있어야 겠죠. 정확히는 "캐릭터의 구조체" 에 캐릭터의 x,y 좌표를 기록하고 있습니다.

 

화면 상으로 아래축이 X 좌표고, 위로 향해있는 축이 Y좌표입니다.

일단은 X좌표를 먼저 찾아보도록 하겠습니다.

 

좌표를 검색할 땐 정수값이 아니라 실수값으로 검색합니다.

그야 좌표값이 정수값이면 캐릭터가 움직일때 0,1,2,3 이런식으로 움직여야 해서 뚝뚝 끊길태니깐요.

 

실수값으로 검색할때 Float로 검색할 지 Double로 검색할지 고민이 많았는데 인터넷 영상에서 풀이하신분들 대부분이 Float로 검색을 하더라구요. 아마 게임 좌표는 Float로 검색하는게 일반적인가 봅니다.

그래도 Double 자체가 Float의 검색 범위를 전부 포함하기 때문에 Float로 해서 찾을 수 있는 좌표를 Double로 해서 찾을 수 있었습니다. 다만 역이 성립하는진 잘 모르겠네요.

 

어쨌던 저는 좌표를 찾기 위해 Float로 검색하도록 하겠습니다!

현재 캐릭터의 좌표를 정확히 알지 못하므로 Unknown initial value(모르는 초기값) 로 검색합니다.

 

게임이 작아서 그런가 값이 얼마 안나오네요 ㅎㅎ; 이제 여기서 값을 줄여보겠습니다.

아래쪽이 X좌표인건 알지만 어느쪽이 양의 방향이고 음의 방향인지 모르므로 Increased 나 Decreased Value는 사용하지 않고 Changed Value를 사용하도록 하겠습니다.

 

왼쪽으로 살짝 가서 X좌표에 변화를 준 뒤 Changed Value

 

다시 오른쪽으로 살짝 가서 X좌표에 변화를 준 뒤 Changed Value

 

이걸 반복해주면 됩니다. 왼쪽으로 살짝 이동하고 Changed Value, 다시 오른쪽으로 살짝 이동하고 Changed Value

 

혹시 하다가 죽어서 캐릭터가 처음 위치로 가도 걱정안하셔도 됩니다. 좌표 값 공간은 메모리상 위치에서 죽을때마다 바뀌는게 아니라 매번 고정이라서요. 만약에 마지막으로 검색한 위치에서 죽어서 처음으로 왔으면 이것역시 X좌표가 바뀐거라 Changed Value로 검색합니다.

 

어느순간에 값이 잘 안줄어든다 싶으면 가만히 있는 상태에서 Unchanged Value (변하지 않는 값) & Repeat(반복)

참고로 캐릭터가 죽지 않는 안전한곳에서 해야 합니다. 이거 돌려놓고 죽으면 좌표가 바뀐 상태서 Unchanged Value 검색이 되서 지금까지 검색한게 망해버립니다.. 실수하지마세요!

 

저 Unchanged Value Repeat 를 돌려놓은 상태에서 점프를 마구 뛰어서 Y좌표 값과 관련된 값도 버려줍니다.

가만히서 점프하는건 아무 문제가 없습니다. Y좌표가 변하는거지 X좌표는 변하는게 아니기 때문에!

 

그렇게 찾다보면 값이 딱 1개만 나옵니다.

이게 X좌표입니다.

캐릭터를 양쪽으로 이리저리 움직여보면 확실히 알 수 있습니다.

 

이제 이 찾아놓은 X좌표를 기준으로 주변 메모리를 살펴봐 구조체를 파악해보도록 하겠습니다.

 

일반적으로 해당 값이 포인터에 의해 바뀌는거면 what accesses 로, 어셈블리 명령어나 레지스터 등을 활용해 직접 바뀌고 있다면 what writes 로 하면 되는데 사실 좌표가 포인터 변수에 의해 바뀌는건지, 직접 바뀌고 있는건진 잘 모릅니다.

 

일단 저는 웬만해서 프로그램 분석시 둘다 보는 편인데 일단 what writes 를 먼저 봅니다.

사실 여기까지 공부하면서 참 혼란이였던게 what writes 에서도 포인터 접근을 찾아내는 경우도 있고 what accesses 에서 레지스터 직접 접근을 찾아내는 경우도 있어서;; 정확히 저 2개 찾기 기능의 차이점이 뭔지 모르겠습니다.

 

일단은 일반적으로 분석할 때 2개다 보게될꺼니깐 크게 중요하진 않습니다.

 

what writes 로 분석을하니 저렇게 2개의 명령어가 보입니다.

저기 Count 가 46으로 많은 명령어가 X좌표가 오른쪽으로 증가할때 (캐릭터가 오른쪽으로 이동시), 그 아래에 있는 명령어는 X좌표가 왼쪽으로 증가할 때 (캐릭터가 왼쪽으로 이동시) 입니다. 

 

재미있는 점은 2개가 같은 명령어인데 다른 위치에서 실행되고 있다는 점입니다.

 

이 중에서 오른쪽으로 캐릭터를 이동했을때 X좌표를 바꾸는 명령어를 한번 보겠습니다.

이 명령어가 공유되고 있는지 확인하기 위해 오른쪽 클릭 - Find out what addresses...

 

오른쪽으로 이동하니 바뀌는 값은 저기 딱 1개밖에 없네요. 우리가 찾아놓은 X좌표 메모리 주소입니다.

다행히 이 코드는 캐릭터 1개가 독립적으로 실행하는 코드로 보입니다 ㅎ

적의 움직임과는 무관하게요. 이를 이용해서 구조체를 파악하면 될 거 같습니다. (캐릭터의 X좌표 메모리 주소)

 

앞에서 하던대로 구조체 분석 시작.

무슨 창이 나오면 전부 OK, YES만 눌러줍니다. (기본 설정으로 사용)

 

그럼 이렇게 구조체가 분석되는데요. 플레이어 X축을 기준으로 바로 아래 (구조체 창에선 28 오프셋) Y좌표가 기록되고 있는걸 알 수 있습니다. 캐릭터 구조체 안에 X, Y 좌표를 기록하고 있는 것이죠.

다만 지금 X좌표를 기준으로 주변 캐릭터 구조체를 파악시켜서 구조체에서 24번 오프셋에 해당하는 Player X 값이 없습니다.

 

(왜 24번 오프셋이 X좌표냐면 그냥 주소상 그렇습니다. 플레이어 Y좌표 주소가 159E2F0 이고, 저희가 찾아놓은 플레이어 X좌표가 0159E2F4 입니다. 정확히 4바이트 차이죠..)

 

수동으로 X좌표를 구조체 목록에 추가시켜주겠습니다. 구조체 아무거나 목록에 대고 Add element

 

지금 분석된 구조체를 기준으로 24번 오프셋이 플레이어 X좌표입니다. Offset에 24적고 Type은 Float로 정한 뒤 OK 합니다 (타입 반드시 지정해주세요.)

 

 

그러면 플레이어 구조체 목록에 이렇게 깔끔하게 X, Y좌표가 들어간걸 확인할 수 있습니다.

 

 

더블 클릭해서 각 X 좌표랑 Y좌표에 설명도 적어주세요. Descrption 아래에 적어주시면 됩니다.

 

구조체 변수에 이름도 붙였습니다.

일단 이로써 캐릭터에 대한 구조체 파악은 끝났습니다.

 

Step2 충돌 검사 무시

앞의 플레이어 구조체를 찾아놓은 상태에서 그대로 시작합니다.

 

이제 플레이어가 적과 충돌해도 죽지 않도록 충돌 검사를 무시해야 하는데요.

 

아마 플레이어가 적과 충돌하면 플레이어 구조체에 isDead 같은 변수가 있어서 죽는 순간에 isDead 가 True 가 되어서 죽고 초기화가 진행되지 않을까? 이런 추정이 됩니다.

 

일단은 뭐가 됐던 플레이어가 죽는 순간에 플레이어 구조체의 값이 어떻게 변화하는지 파악해야 합니다.

 

충돌을 한 순간에 구조체에서 변화한 값이 많이 보이는데요.

 

여기서 나름 의미를 보이는 값은 이 값인 거 같습니다. 죽었더니 갑자기 1로 바뀌었네요.

다시 살아나니깐 0으로 돌아가구요

 

오프셋 40번째에 있는 값인데 Reset State 라는 이름을 붙였습니다.

그리고 이 값을 1로 한번 바꿔보니 바로 게임이 처음으로 초기화 되고 0으로 다시 돌아가는걸 알 수 있습니다.

 

으음.. 그러면 혹시 저 값을 0으로 계속 고정하면 게임 초기화가 되지 않으니 죽지않을까? 생각이 드네요

 

이걸 주소 리스트에 추가하도록 하겠습니다.

 

그리고 0으로 Freeze 합니다.

 

예.. 죽지 않긴 하는데 뭔가 이상하네요 ㅋㅋㅋ

캐릭터는 분명 사라졌는데 초기화가 안되서 일단 움직여지고 밟판도 밟아집니다.

이걸 죽었다고 해야하나 살았다고 해야하나?

 

말그대로 살아있는 시체가 되어버렸습니다 ㅠㅠ

이건 저희가 원한게 아니죠.

 

이번에도 이 Reset State 에 대고 값이 어떻게 바뀌는지 추적하기 위해 Find out what wrties to this address..

 

죽는 순간에 mov 명령어가 들어오면서 해당 공간에 값을 1로 설정하네요.

Show disassembler를 눌러서 이 명령어를 따라가봅시다.

 

그러면 이렇게 mov 명령어가 있는 위치로 이동해서 어셈블러를 보여주는데요.

위에 점프문이 보입니다. 그럼 여기서도 다시 생각을 해보는건데 아마 저 점프문이 캐릭터가 죽었는지 검사하고 초기화를 하는데 관여하는 명령어가 아닐까? 하는 추측입니다.

 

위에 점프 흐름을 not equal 에서 equal 로 바꿔보겠습니다. (명령어에 더블클릭 후 바꾸면 됩니다)

이러면 죽어서 초기화 하는 명령어 자체가 실행이 안될 거 같아요!

 

예~~ 이제 충돌해도 캐릭터가 절대 죽지 않습니다. 일단 충돌 방지는 마무리. 사실 이거만 해도 이 문제는 이미 깼습니다.

아쉬우니깐 텔레포트랑 무한 점프 치트도 해볼게요

 

Step3 텔레포트

텔레포트는 음청 쉽습니다. 아까 구조체에서 Player Y만 오른쪽 클릭 - Add to address list 로 추가한담에

 

X좌표랑 Y좌표를 바꿔주시면 됩니다.

 

참고로 Y좌표를 -1로 바꿔버리면

 

게임엔진이 처리를 해서 이렇게 위에서 떨궈버립니다.

일정 이상 큰 좌표를 넘어가게 입력하면 영원히 게임 밖으로 나가는게 아니라 게임 엔진이 처리를 해서 다시 돌려보냅니다.

저기 왼쪽이 점프로 올라가기 어려운데 Y좌표를 -1로 바꿔서 텔포해서 위에서 떨어진다음에 발판 활성화 해주면 됩니다.

 

Step4 무한 점프

게임 치트를 많이 써보신 분들은 아마 "무한 점프" 도 몇 번 보셨을 건데요. 말 그대로 캐릭터가 점프할 때 제한 없이 계속 뛸 수 있게 해주는 겁니다. 

 

이미지 출처 : https://www.reddit.com/r/Mario/comments/94pmqb/mario_jump_render/

게임마다 연속으로 점프를 허용하는 횟수는 다릅니다만, 특별한 게임이 아니면 대부분 1단, 2단 점프가 끝입니다. 그리고 위 사진인 마리오는 대표적인 1단 점프 게임이죠. 2단 점프는 허용하지 않습니다.

 

이미지 출처 : https://www.oreilly.com/library/view/practical-game-design/9781787121799/ff5746a2-1741-46f8-a09a-bddd43528497.xhtml

1단 점프 게임인 마리오의 입장에선 당연하지만 점프 중엔 다시 점프를 하지 못하게 막아야 합니다.

이를 프로그래밍 적으로 구현하려면 어떻게 해야할까요?

 

"isjump" 라는 bool 변수를 하나 둬서 캐릭터가 땅에 있을땐 False, 점프 중엔 True를 줘서, 점프를 누르면 isjump 값을 검사해서, true 라면 다시 점프가 되지 않게 해야겠죠.

 

하지만 이렇게 점프를 여러번 못하게 막아서 1단 점프를 구현하는건 여러가지 방법이 있습니다.

 

사실 점프중인지 아닌지 변수에 기록할 수도 있고 (isjump)

땅에 있는지 아닌지 기록할수도 있습니다 (isground) 이러면 앞의 isjump랑 값이 반대가 됩니다.

 

isjump 형태로 구현하면 땅에 있을때 0, isground 형태로 구현하면 땅에 있을때 1이 되죠.

이외에도 점프 높이에 따라 0, 1, 2가 될 수도 있고 랜덤한 정수값이 될 수도 있습니다.

 

치트엔진으로는 저 점프 상태를 검사하는 정수값을 찾아서 변조해야 무한 점프를 성립시킬 수 있는데요. (bool 이라는 자료형이 애초에 어셈블리어엔 없으니깐요. 컴퓨터가 보기엔 숫자입니다. True = 1 , False = 0)

 

찾는 입장에선 땅에 있을때 0, 점프할때 1로 찾아서 값을 못찾을 가능성이 있다는거죠. Case2나 Case3 같은 상황이면요. 예를 들어서 컵헤드라는 게임은 땅에 있을때 0이고 , 점프할때 2입니다 ㅋㅋ. 어떨땐 1이 되기도 하죠.

땅에 있을때 0, 점프할때 1로 찾으면 절대 못찾습니다.

 

하지만 이러한 사실이 그렇게 문제가 되진 않습니다. 어쨌던 땅에 있을때랑, 점프할 때 저런 값이 "달라진다"는건 확실하니깐 치트엔진에서 값이 바뀐걸 포착하는 Changed / Unchanged Value로 검색을 하면 됩니다.

 

가만히 있을땐 Unchanged Value로, 점프할땐 게임을 멈추고 Changed Value로 검색하면 됩니다!

 

이후 값을 찾는데 성공했다면

그리고 점프를 검사하는 값을 항상 땅에 있는 상태로 고정해서 속이면, 점프를 해도 땅에 있는 상태가 되서 계속 무한 점프가 되겠죠!!

 

점프 상태를 검사하는 bool 값 (True / False 일반적으로 이런걸 flag 값이라고 표현 깃발(flag) 은 드는것 = 1 과 내리는것 = 0 밖에 없기 때문) 을 찾아보겠습니다.

True는 1이고, False는 0이라고 약속되어 있으므로 4 Bytes 정수 값, Unknown initial value로 찾습니다.

 

점프(space)를 뛰고 P를 눌러서 정지합니다.

반드시 정지를 하고 찾아주세요. 점프를 하고 나서 언제 플래그 값이 설정될 지 모릅니다. 

 

바닥에 있다가 점프 상태가 되었으므로 변화된 값을 찾습니다. 

Changed Value로 검색!!

 

정지를 풀고 땅에 착지시킵니다.

 

땅에 착지해서 점프 상태가 다시 변화했으므로 Changed Value로 검색.

 

또 Unchanged value로 Repeat를 걸어놓은 상태에서 점프는 하지 않고 이리저리 와리가리 칩니다.

 

위 과정을 반복.

 

노가다 하다보면 값이 한 30개로 줄여집니다.

여기서 적당히 점프 상태를 검사하는 변수 값이 뭔지 감으로 찾아내야 합니다.

 

 

점프를 뛰면서 값의 변화를 보면 되는데 아마 이거 인 거 같네요.

바닥에선 0이고, 점프를 뛰면 1로 바뀝니다.

 

해당 게임은 점프 검사시 Case1의 방법을 사용합니다.

점프하지 않고 땅에 있으면 0 , 점프중이면 1

 

무한 점프를 뛰려면 항상 바닥이라고 속이면 되겠죠. 0인 상태서 고정해버립니다.

 

네 ㅋㅋ 점프키를 꾹 누르면 캐릭터가 무한 점프를 하는걸 알 수 있습니다!!

무한 점프까지 완료!!

 

참고로 무한 점프할 때 조금 딜레이가 있을건데 치트엔진에서 값을 고정시키면 값이 바뀔때마다 원래 값으로 계속 다시 쓰는데 이거에 딜레이가 좀 있어서 그렇습니다.

 

 

이게 싫으면 해당 점프 상태값을 1로 쓰는 명령어를 NOP으로 패치해버리면 됩니다. 이러면 애초에 점프 상태를 1로 만들 수 없어서 치트엔진 값 고정보다 훨씬 빠르게 무한 점프가 됩니다

 

GOOD!!

 

이렇게 하면 클리어입니다.

원래 제대로 했으면 저기 적들이 문앞에 달라붙어서 대기를 타야하는데

제가 위에서 점프문을 조작하고 해당 코드가 망가진 거 같습니다 ㅎㅎ;

뭐 어쨌던 죽지 않고 캐릭터가 문앞에 가기만 하면 됩니다

 

클리어! 모두 따라오느라 고생 많으셨습니다 :) 치트엔진 튜토리얼 시리즈는 여기서 진짜 완결입니다.

끝까지 문제풀이를 해서 뿌듯하구요. 모두 즐거운 하루 되시길 바랍니다!

SNS 공유하기
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.