본문 바로가기

기타 잡 코딩

슈퍼마리오 1을 플레이하는 AI를 만드는 방법(3)

이번 시간에는 드디어 python을 사용할 예정이다.

 

2부에서 찾았던 주소들을 python으로 읽기 전에 주소들을 다시한번 정리하자.

 

================

마리오의 x위치는 0086 006D이다

마리오의 y위치는 00CE

마리오의 x속도는 0057

마리오의 y속도는 009F

마리오의 상태는 0754 0756 : 꼬마는 10 성인은 01 불쏘는 성인은 02

각 몬스터의 x위치는 0087 0088 0089 008A 008B 

                           006E 006F 0070 0071 0072

각 몬스터의 y위치는 00CF 00D0 00D1 00D2 00D3

각 몬스터의 종류는 0016 0017 0018 0019 001A

점수: 07DD ~ 07E4 (BCD 코드)

시간: 07F8 ~ 07FA (BCD 코드)

앞으로 나아간 정도: 071B 071C

stage: 075C 075F

입력 커맨드: 06FC

========================

 

python에서 특정 프로세스의 메모리를 읽거나 쓰는 다양한 방법이 존재하지만,

 

ReadWriteMemory 라는 라이브러리를 이용하는 것이 가장 간단하고 직관적이다.

 

아래 링크를 참조해서 다운받고 사용법을 익히자.

 

https://pypi.org/project/ReadWriteMemory/

 

from ReadWriteMemory import ReadWriteMemory

rwm = ReadWriteMemory()
process = rwm.get_process_by_name('VirtuaNES.exe')
process.open()
base_address = 0x0059E3B0
offset = 0x07FA
address = base_address + offset
time = process.read(address)
print(time)

위 코드는 마리오 게임의 남은시간의 일의 자리 숫자를 출력하는 코드이다.

 

남은 시간
실행 결과

메모리 뷰어에서 보았던 주소들은 사실 위 base address인 0x0059E3B0에서 시작한다.

 

따라서 지난 글에서 찾았던 다양한 값들의 주소는 base address에 더해서 주소를 구해야한다.(offset)

 

process.write(address, 9)

해당 주소에 직접 쓸 수도 있다.

 

실행하면 남은 시간의 일의 자리 숫자가 9로 바뀐다.

 

 

자 이제 요리조리 해서 시각화를 하자.

 

먼저, 정적 정보의 시각화를 해본다.

 

    def get_name_table(self):
        name_table = np.zeros((26,16))
        for offset in self.offsets['name_table']:
            address = self.base_address + offset
            value = self.process.read(address)
            value = value.to_bytes(4, 'big')[3]
            i = (offset-0x0500) // 16
            j = (offset-0x0500) % 16
            name_table[i, j] = value
        name_table_reshaped = np.zeros((13,32))
        name_table_reshaped[:,:16] = name_table[:13,:]
        name_table_reshaped[:,16:] = name_table[13:,:]
        return name_table_reshaped

    def visualize_name_table(self):
        name_table = self.get_name_table()
        name_table = name_table.astype(np.uint8)
        name_table_image = cv2.applyColorMap(name_table, cv2.COLORMAP_VIRIDIS)
        name_table_image = cv2.resize(name_table_image, (None, None), fx=20, fy=20, interpolation=cv2.INTER_NEAREST)
        cv2.imshow('', name_table_image)
        cv2.waitKey()

현재 맵이 numpy array로 변형되었고, 위 처럼 시각화 되었다.

원본은 위 이미지와 같다.

 

이제 저 위 정적 정보 위에 마리오를 올려보자.

 

 

 

마리오의 위치가 실시간으로 잘 반영된다.

 

몬스터를 나타내고, 카메라가 보고 있는 부분만 크롭하도록 하자..

 

 

이제 몬스터도 잘 보이고 현재 화면 기준으로 카메라가 잘 따라온다.

 

 

가끔 이 상태로 플레이하다 보면 개발자가 만든 실수나 이스터 에그를 발견할 수 있다.

 

보이지 않는 몬스터의 영혼(?)들을 볼 수 있다;;

 

 

4부에서 계속...