원문: https://macabeus.medium.com/game-decompilation-using-ai-4d47b65f8852 (Translated by Google Gemini)


안녕하세요, 저는 Macabeus입니다. 이 글에서는 제가 오래된 게임을 디컴파일하기 위해 AI를 사용한 개발 여정을 공유하고, 이 재미있는 분야에서의 개인적인 연구를 탐색해 보려고 합니다!

게임 디컴파일이란?#

간단히 말해, 디컴파일은 저수준 코드를 고수준 코드로 변환하는 과정입니다. 예를 들어, 어셈블리에서 C로 변환하는 것입니다.

IDA Pro나 Ghidra와 같은 유명한 리버스 엔지니어링 도구들을 통해 이를 알고 계실 수도 있습니다. 하지만 이 글에서 제가 디컴파일이라고 언급할 때는, 일치하는 디컴파일(matching decompilation) 에 대해 이야기하는 것입니다. 이 더 구체적인 용어는 컴파일했을 때 대상 어셈블리와 정확히 일치하면서도, 인간이 읽을 수 있는(human-readable) 고수준 코드를 작성하고자 함을 의미합니다.

Ghidra나 유사한 도구들의 디컴파일러는 주로 참고용으로 유용합니다. 어셈블리를 분석할 때 도움이 되지만, 컴파일했을 때 동일한 바이너리를 생성하는 고수준 소스 코드를 만들어내는 데는 적합하지 않습니다.

원본과 정확히 동일한 바이너리를 생성하는, 가독성 높은 고수준 C 코드를 갖게 되면 오래된 게임에 두 번째 생명을 불어넣을 수 있습니다. 코드가 읽기 쉬워지면 탐험, 커스터마이징, 그리고 새로운 가능성의 문이 열립니다!

예를 들어, 완전히 디컴파일된 최초의 주요 게임 중 하나는 슈퍼 마리오 64였으며, 이는 PC로 포팅하여 고화질로 실행하는 것과 같은 놀라운 커뮤니티 프로젝트의 길을 열어주었습니다. 여기에서 더 자세한 내용을 읽어보실 수 있습니다.

여정의 시작#

저는 항상 게임 리버스 엔지니어링을 즐겨왔습니다. 저의 첫 프로젝트 중 하나는 Klonoa: Empire of Dreams를 위한 레벨 에디터였습니다. 다음 단계로, 이 게임 자체를 디컴파일해 보는 것이 흥미로울 것이라고 생각했습니다! 물론, 이렇게 큰 작업에 뛰어들기 전에, 실제로 게임 디컴파일이 어떻게 작동하는지 배워야 했고, 진행 중인 프로젝트를 돕는 것이 좋은 접근법이 될 것이었습니다.

시작하기 위해, 저는 Sonic Advance 3(GitHub)의 디컴파일에 기여하기로 결정했습니다. Klonoa 게임처럼 GBA 게임이고, 디컴파일해야 할 함수가 많이 남아있었기 때문입니다.

저의 첫 번째 풀 리퀘스트(pull request)에서는, Marun이라는 이름의 적을 디컴파일하는 작업을 했습니다. 프로젝트의 주요 기여자들의 안내에 따라 이 모듈을 선택했는데, 그들은 SA3의 적 코드가 비슷한 패턴을 따르는 경향이 있다고 지적했습니다. 이는 이전에 디컴파일된 적들을 참고 자료로 볼 수 있다는 것을 의미했고, 견고한 출발점이 되었습니다.

처음에는 다른 기여자들과 동일한 접근 방식을 따랐습니다: decomp.me를 사용하고, scratch를 만들고, 수동으로 어셈블리 코드를 디컴파일했습니다. 하지만 힘들었습니다. 저는 C나 어셈블리에 능숙하지 않아서, 이 작업은 제가 아직 갖추지 못한 많은 컨텍스트와 직관을 필요로 했습니다.

일치하는 디컴파일 101: “scratch”란 무엇을 의미하나요?#

scratch는 작업 단위를 의미합니다. 여기에는 게임의 대상 어셈블리(target assembly), 여러분의 고수준 코드, 그리고 여러분의 코드를 컴파일하여 생성된 현재 어셈블리(current assembly) 가 포함됩니다.

얼마 후, 저는 이런 생각을 하게 되었습니다: “어쩌면 AI를 사용해서 디컴파일을 도울 수 있지 않을까? 결국, 이 과정은 많은 패턴 매칭을 포함하는데, 이는 바로 AI가 잘하는 것이다.”

저는 대부분 간단하고 반복적인 명령어로 구성된 함수를 선택했고, 당시 최신 버전이었던 Claude Sonnet(2025년 1월 초)의 Sonnet 3.5에 기본적인 프롬프트를 사용해 보기로 결정했습니다. 놀랍게도, AI는 첫 시도에서 96% 일치 를 달성하는 코드를 생성했습니다! scratch채팅 내용을 확인해볼 수 있습니다. 출력된 코드가 진정으로 인간이 읽을 수 있으려면 다듬어야 했지만, 처음부터 96%를 달성한 것은 함수를 디컴파일하는 데 환상적인 출발점을 제공합니다.

일치하는 디컴파일 101: “match"란 무엇을 의미하나요?#

일치하는 디컴파일에 대해 이야기할 때, “% match"는 특정 어셈블리 비교 알고리즘을 사용했을 때 여러분의 현재 어셈블리(current assembly)대상 어셈블리(target assembly) 와 얼마나 가까운지를 의미합니다.

목표는 100% 일치를 달성하고(즉, C 코드가 정확히 동일한 대상 어셈블리로 컴파일됨) 가독성 있는 C 코드를 갖는 것입니다.

그 후, 저는 다음 함수들을 위해 프롬프트를 개선하는 데 집중했습니다:

  • 다른 적에서 비슷하게 디컴파일된 함수들을 예제로 찾아보았습니다. 특히 제가 디컴파일하려는 Marun의 함수와 동일한 내부 함수를 호출하는 경우에요.
  • 이러한 예제들과 함께, Marun 구조체나 다른 중요한 정의와 같은 관련 컨텍스트도 프롬프트에 포함했습니다.

불행히도, 여러 번의 시도에도 불구하고 96% 일치만큼 좋은 결과를 재현할 수는 없었습니다. 첫 결과가 그저 운이 좋은 예외였을지도 모른다는 느낌이 들기 시작했습니다…

하지만 그때, 1월 20일에 유망한 일이 일어났습니다: DeepSeek가 R1 모델을 출시했는데, 이는 무료로 사용할 수 있는 최초의 공개 추론 모델이었습니다. 저는 즉시 테스트했고, 결과는 인상적이었습니다. Sonnet 3.5로 얻었던 것보다 훨씬 좋았습니다.

예를 들어, 이 프롬프트로, DeepSeek는 74% 일치하는 코드를 반환했습니다. 그리고 수정은 간단했습니다: memsetCpuFill16으로 바꾸는 것만으로 100% 일치를 달성했습니다!

Marun 모듈을 디컴파일한 제 경험상, DeepSeek R1은 꾸준히 최소 ~60% 일치하는 코드를 출력합니다.

단순히 좋은 출발점을 제공하는 것 이상으로, 코드를 개선하는 데도 도움이 되었습니다. 일치율을 높이거나, 일치율을 유지하면서 가독성을 향상시키는 방식으로요. 예를 들어, goto를 피하는 데 도움이 되었습니다.

물론, AI 코드는 완벽하지 않습니다. 반복되는 문제 중 하나는 매크로의 부재였습니다. 예상된 일입니다: AI는 프롬프트에 포함되거나 다른 곳에서 제공되지 않는 한 프로젝트별 매크로에 대해 본질적으로 알지 못하며, 매크로는 어셈블리에 나타나지 않기 때문에 놓치기 쉽습니다. 저 자신도 프로젝트에 대한 완전한 컨텍스트가 부족했기 때문에, 프롬프트에 포함해야 할 매크로가 누락되었을 때 항상 깨닫지는 못했습니다. 코드 리뷰 중에, 이것이 개선점으로 지적된 주요 영역 중 하나였습니다.

저는 1월 6일에 Marun 모듈 디컴파일 작업을 시작했고, 여가 시간에만 작업하여, 1월 31일에 마침내 PR이 병합되었습니다. 이는 SA3 전체 진행률의 0.3%에 해당합니다.

AI + decomp.me#

Marun 모듈에 대해 100% 일치를 달성한 후, 저는 decomp.me에 AI를 추가하는 작업을 시작했습니다. 제 목표는 더 많은 사람들을 참여시키고 디컴파일을 위한 AI 워크플로우를 향상시키는 것이었습니다. 좋은 예제를 자동으로 프롬프트에 포함시키는 것과 같은 일부 기능은 더 깊은 통합이 있어야만 실용적입니다.

저는 주로 새로운 scratch를 시작하기 위해 AI를 사용했기 때문에, 그 특정 사용 사례를 중심으로 UX를 설계했습니다(여기서 데모 비디오를 볼 수 있습니다). 풀 리퀘스트에 대한 일부 피드백 후, UX는 채팅 기반 접근 방식으로 재설계되었습니다. 하지만, 사용자들이 정확히 어떻게 이점을 얻을지, 그리고 시작하는 데 따르는 어려움, 특히 사용자가 AI 제공업체의 유료 API 키를 입력해야 한다는 점 때문에 PR은 병합되지 않았습니다.

하지만 이제 우리에게는 유망한 신기술이 있습니다: 모델 컨텍스트 프로토콜(Model Context Protocol, MCP). MCP를 사용하면 decomp.me에 AI 지원을 더 원활하고 사용자 친화적인 방식으로 추가하는 것이 가능할 수 있습니다.

예를 들어 MCP + Ghidra를 사용하는 경우와 같이, 디컴파일 도구에 MCP를 추가하는 방법에 대한 영감이 될 수 있는 사례들이 있습니다. 이것은 나중에 확인해 볼 것입니다!

마지막 생각들#

이 첫 경험에서 몇 가지 핵심적인 교훈이 두드러집니다:

  1. 저는 매우 특정한 맥락에서 AI를 테스트해 왔습니다: Sonic Advance 3의 적 모듈을 디컴파일하는 것입니다.
  2. 이 좁은 사용 사례에서, AI는 믿을 수 없을 정도로 효과적이었습니다! 많은 함수에 대해 강력한 출발점을 제공했고, C와 어셈블리에 대한 저의 제한된 유창함에도 불구하고, 전체 모듈을 상당히 빠르게 디컴파일할 수 있었습니다.
  3. 그렇긴 하지만, 이것은 매우 AI 친화적인 시나리오였을 가능성이 높습니다. 제가 작업하던 모듈과 유사하고 이미 디컴파일된 다른 모듈들이 있었기 때문에, 프롬프트에 고품질의 예제를 포함할 수 있었습니다. 또한, GBA는 오래된 콘솔이기 때문에 컴파일러도 더 단순하고 예측 가능합니다.
  4. 이 워크플로우를 더 현대적인 게임에 적용하려면, 더 많은 연구와 도구가 필요할 것입니다. 예를 들어: 특정 콘솔이나 게임 데이터에 대한 모델을 파인튜닝(fine-tuning)하거나, 다양한 관련 예제를 자동으로 포함시키거나, MCP와 같은 프로토콜을 통해 AI를 통합하여 구조화되고 맥락에 맞는 지원을 제공하는 것입니다.

지금은 여기까지입니다! 보시다시피, 여기에는 개선의 여지와 기회가 많습니다. 이 도전에 유용할 수 있는 AI 분야의 새로운 소식이 항상 있다는 것은 흥미로운 일입니다.

저의 궁극적인 목표는 오직 AI만을 사용하여 게임을 완전히 디컴파일하는 것입니다. 야심 차다고요? 물론입니다. 재미있다고요? 그것도 물론이죠! 한번 실현해 봅시다! 😄


저자/원문 관련 링크들: