Javascript로 포켓몬(골드) 세이브 에디터 만들기 (next.js, node.js)

김지환·2023년 2월 19일
0
post-thumbnail


어릴적 누구나(?) hex editor를 이용해서 게임 세이브 파일을 에디트 해본적이 있을 것이다.

닌텐도 게임보이™️로 출시됐던 포켓몬스터 시리즈는 내가 중학생 시절 엄청난 열풍을 불러 일으켰다.

이 글을 시작으로 포켓몬스터 골드버전 에디터를 nextjs를 이용하여 만드는 시리즈를 연재 하려고 한다.

가장 먼저 시작해야할 일은 세이브 파일에서 어떤 위치를 수정해야하는지 찾는 것이다.

일단 소지금부터 접근해보기로 하자. 세이브 파일은 구글링을 통해서 구한 파일이고 다행히 금액이 특정하기 좋은 숫자인것 같다.

199447은 16진수로 0x30B17 이고 이를 헥스 에디터를 통해 검색 해보겠다.

두개의 주소에서 위의 값이 검색 되었다.
1. 0x0C6D
2. 0x23DB

한개의 주소값만 나왔다면 확실하겠지만 두개 정도만 되어도 난이도가 쉬운 편이다. 199447이란값이 웬만해선 중복되진 않을 것이기 때문에 검증을 통해 사실을 확인해보자.

게임 내 상점에서 물건을 사서 돈의 데이터를 바꾸고 저장한뒤 다시 찾아본다.

1200원 짜리 울트라 볼을 사서 저장했다.
그리고 다시 검색했다.
1. 0x0C6D
2. 0x23DB

위의 결과값과 똑같게 나왔다.

세이브 파일에 이런식으로 두개씩 중복해서 데이터를 저장하고 검증하는 용도로 사용하는것을 많이 보았다.

어쨌든 이런 인과관계를 모두 파악하려면 무수한 삽질을 통해서 알아내야하는 매우 고단한 작업이다...

다행히도 구글링을 통해 poke gold (2세대)의 세이브 파일 스트럭쳐가 정리되어 있는 위키를 찾았다.

hex 에디터에서 검색한 주소중 두번째 값(0x23DB)과 위키에 적혀 있는 부분과 일치한다.

Although all data appears twice in the save file, only the primary copy is documented below.

모든 데이터가 두번씩 저장된다고 한다.
간단한 테스트등을 통해 얻은 내용과 사이트의 내용들을 정리해보자면

  1. primary 데이터와 secondary 데이터중 하나라도 checksum이 검증되면 그 데이터를 사용한다. 단 primary가 우선권이 있다.
  2. 두 데이터 모두 checksum이 맞지 않으면 세이브 파일이 오염되었다 판단하고 게임을 처음부터 시작하여야 한다.
  3. 체크섬은 0x2009 ~ 0x2D68 (primary checksum) 의 값을 모두 더하고 만약 2바이트가 넘는다면 캐리니블을 버려주고 리틀 엔디언으로 0x2D69 주소에 저장한다.

체크섬을 모두 더하기 위해 간단하게 파일을 입력받고 읽어들여 계산하는 코드를 작성해 보았다.

const fs = require("fs");
fs.readFile("./realgold.sav", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  const CHECKSUM_START_OFFSET = 0x2009;
  const CHECKSUM_END_OFFSET = 0x2D68;
  let sum = 0;
  for (let i = CHECKSUM_START_OFFSET; i <= CHECKSUM_END_OFFSET; i++) {
    sum += data[i];
  }
  console.log(sum.toString(16));
});
  • 0x2009 ~ 0x2D68 더한값 = 0x208DA
  • 캐리 니블을 버린값 = 0x08DA
  • 리틀 엔디언 적용 = 0xDA08


정확하게 사이트에 나온 offset(0x2D69)에 2바이트 리틀 엔디언으로 DA 08 이 저장 되어있다.

원리를 간단히 익혔으니 수기로 돈을 에디트 해보자!

  1. 123456을 16진수로 변환 (0x1E240)
  2. 헥스 에디터를 통해 0x23DB 주소값(돈은 총 3바이트를 차지한다)을 수정후 저장
  3. nodejs로 체크섬 계산 (필요시 캐리니블 버림)
  4. 0x2D69 에 2바이트 리틀 엔디언으로 체크섬값 저장


성공이다.

profile
return 0

0개의 댓글