2019년 8월 14일 수요일

터틀몬 어드벤쳐 ROM 추출 프로젝트 (4부: 덤프 장치)


 이전 글에서 칩에 대해 조사를 하였다. 이제 ROM을 덤프하기 위한 장치를 제작할 차례이다. 필자가 가지고 있는 장비인 TL866A는 핀 수가 부족하였고, 여기에 맞는 설정도 찾기가 곤란하여 그냥 따로 만들어보기로 하였다.

1. 목표

 칩 내부의 데이터를 추출할 수 있는 덤프 장치를 만든다.

2. 사전 조사

 먼저 이 칩과 궁합이 맞을 마이크로컨트롤러를 선택해야 했다. 핀 수도 충분해야 하고, 칩의 논리 HIGH전압인 3.3V와도 레벨이 맞아야 한다. 일단 핀 수만 봐도 자동으로 전압 레벨이 3.3V으로 내려간다. 지금 갖고 있는 아두이노 메가, Teensy3.5 둘 다 3.3V에서 작동한다. 여기서 필자는 당연히 Teensy를 선택했다. 클럭 속도만 봐도 7배 이상 차이가 난다. 거기다 SD카드 슬롯이 기본으로 장착되어 있어 롬을 편하게 덤프할 수 있을 것이다. (사실은 아두이노 메가를 여기에 쓸려고 반년 전에 구매했으나 프로젝트 시작을 미루는 동안 다른 프로젝트 때문에 Teensy를 구매하게 되어버렸다. 하하...)

3. 실행


대략적인 핀맵

 결국은 핀을 전부 칩에 납땜해야 할 것이므로 깔끔한 코드보단 납땜을 편하게 하는데 더 중점을 두었다. 위 사진에서 보다시피 단순한 배치이다. (주소핀 가운데 빈것은 3.3V 핀이다)

 그래도 코드는 단순할 것이다. 주소 변수의 각 비트를 주소 핀으로 내보내고, 칩에서 오는 데이터를 데이터 핀으로 읽어 SD카드에 저장하는 작업을 반복하는 것이다. 물론 데이터 버퍼도 만들어야 할 것이다.
 말은 이렇게 했지만 Teensy의 SD카드 슬롯을 사용해본 적은 한번도 없어 막막했다. 여기서 가장 좋은 방법은 이미 만들어진 것을 참고하는 것이다. Teensy보드 기본 예제에 SD카드를 사용하는 예제는 없었기에 검색을 해보다 TeensySdioDemo라는 프로그램을 찾게 되었다. 같은 사람이 만든 SdFat 라이브러리를 기초로 하고 있었다.

깃허브 페이지

  일단 라이브러리와 같이 다운받아 실행하여 보았다.

SdFatSdio벤치마킹을 진행하는 모습 (너무 느려서 멈춘줄 알았다)

 알고보니 SdFatSdioEX와 SdFatSdio를 각각 이용한 읽기/쓰기 속도를 측정해주는 벤치마킹 코드였다. 시리얼 창에서 1이나 2를 입력하여 시험해볼 수 있었다. 뭔가 SD카드와의 통신 방식이 다른 것인지 SdFatSdioEX가 훨씬 빨랐다. 이걸로 해야겠다. 그리고 벤치마킹 프로그램이니 SD카드에 파일을 쓰는 코드도 같이 들어있을 것이다. 딱 맞는 예제를 찾은 것 같다.

코드 편집은 다음과 같이 이뤄졌다.

  1. SdFatSdioEX만 이용하도록 SdFatSdio를 지운다.
  2. 벤치마킹하는 코드 중 파일을 만드는 코드만 남기고 전부 삭제한다.
  3. 새로운 코드를 삽입한다.

 첫번째 단계는 매우 간단했다. 둘이 완전히 분리된 코드를 가지고 있었다. 다른 말로 SdFatSdioEX전용 코드가 있으면 그 어딘가에 똑같은 SdFatSdio 전용 코드가 있다는 말이다. 코드 위쪽의 주석에서도 나오듯이 원래 둘은 한 프로그램 안에 같이 쓰기가 어려운지 분리하는데 많은 신경을 쓴 모양이다.

서로 대응되는 코드

 두번째 단계에서는 코드가 어떻게 작동하는지 하나하나 뜯어보았다. 코드 옆에다 주석을 달다 보니 그리 오래 걸리지 않아 해석이 되었다.(다만 변수 이름이 이상한 것은 적응하기 힘들었다.) 
 이 중 file.open() 함수의 "O_RDWR | O_CREAT"부분은 해석이 완전히 되지 않아 따로 찾아보았더니, 첫번째는 파일을 읽기/쓰기 모드로 연다는 것이었고 두번째는 파일이 없으면 새로 만든다는 뜻이었다. 읽기는 할 필요가 없으므로 첫번째 것은 완전 쓰기 모드인 O_WRITE로 변경하였다.

라이브러리 내부에 있는 도움말 페이지. 무려 검색기능을 지원한다

 세번째 단계는 항상 하듯이 느긋하게 작성하였다. 주소를 핀으로 내보내는 함수는 따로 만들어 두고, 핀으로 데이터를 입력받아 1Mbit 버퍼에 저장한 뒤 SD카드에 쓰는 부분부터 작성하였다. 사실 그러다보니 금방 끝났다. 마지막으로 덤프할 용량을 시리얼로 입력받는 코드까지 작성하고 코딩은 여기서 끝냈다. 아래는 완성된 코드인데, 너무 들떴나 보다. (최종 코드는 수정하여 GitHub에 있다.)

// Simple performance test for Teensy 3.5/3.6 SDHC.
// Demonstrates yield() efficiency.

// Warning SdFatSdio and SdFatSdioEX normally should
// not both be used in a program.
// Each has its own cache and member variables.

//Modified by yclee126 to ROM dump program.

#include "SdFat.h"

// 1 Mb (125 KB) buffer
const size_t BUF_DIM = 125000;

// 32 Mb (4000 KB) file
const uint32_t FILE_SIZE = 32UL * BUF_DIM;

SdFatSdioEX sdEx;

File file;

uint8_t buf[BUF_DIM];

bool useEx = true;

void errorHalt(const char* msg) {
  sdEx.errorHalt(msg);
}

void startDump(uint8_t* romSizeMb) {
  if (!file.open("ROM.hex", O_WRITE | O_CREAT)) { //파일을 쓰기 모드로 연다.(WRITE) 파일이 없으면 파일을 생성한다.(CREAT)
    errorHalt("open failed");
  }
  file.truncate(0); //파일을 초기화한다.

  uint32_t t = micros();
  uint32_t adr = 0;
  for (uint8_t n = 0; n < *romSizeMb; n++) {

    for (size_t a = 0; a < BUF_DIM; a++) {
      adrWrite(&adr);
      delayMicroseconds(1);
      
      for (uint8_t i = 16; i < 24; i ++) {
        bitWrite(buf[a], i-16, digitalRead(i));
      }
      adr ++;
      
    }
    
    if (BUF_DIM != file.write(buf, BUF_DIM)) {
      errorHalt("write failed");
    }
    Serial.print(n+1);
    Serial.print(" / ");
    Serial.println(*romSizeMb);
  }

  t = micros() - t;
  Serial.print("Run time (sec): ");
  Serial.println(t / 1000000.0);
  //file.rewind(); //Just to remind, this uses "position" instead of "pointer"

  file.close();
  Serial.println("Done! Sweet ;)");
  while (1);
}

void adrWrite(uint32_t* adr){

  for (uint8_t i = 0; i < 13; i ++) {
    digitalWrite(i, bitRead(*adr, i));
  }
  for (uint8_t i = 24; i < 33; i ++) {
    digitalWrite(i, bitRead(*adr, i-11));
  }
  
}
//-----------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);

  //Address output pins
  for (uint8_t i = 0; i < 13; i ++) {
    pinMode(i, OUTPUT);
  }
  for (uint8_t i = 24; i < 33; i ++) {
    pinMode(i, OUTPUT);
  }

  //Data input pins
  for (uint8_t i = 16; i < 24; i ++) {
    pinMode(i, INPUT);
  }

  while(!Serial);

}
//-----------------------------------------------------------------------------
void loop() {
  do {
    delay(10);
  } while (Serial.available() && Serial.read());

  Serial.println("ROM dump program by yclee126");
  Serial.println("using heavily modified TeensySdioDemo :)");
  Serial.println("SdFatSdioEX is used for accessing SD card.");
  Serial.println("Type your ROM size in Mb to dump your ROM. Maximum is 255");
  Serial.println("");
  while (!Serial.available()) {
  }
  uint8_t romSizeMb = 0;
  uint8_t d1 = Serial.read();
  uint8_t d2 = Serial.read();
  uint8_t d3 = Serial.read();
  if(d2 == 255){
    romSizeMb += d1-48;
  }
  else if(d3 == 255){
    romSizeMb += d2-48 + (d1-48)*10;
  }
  else{
    romSizeMb += d3-48 + (d2-48)*10 + (d1-48)*100;
  }
  Serial.print("The given value is ");
  Serial.print(romSizeMb);
  Serial.print("Mb (");
  uint32_t inByte = 125000UL*romSizeMb;
  Serial.print(inByte);
  Serial.println("byte)");

  if (!sdEx.begin()) {
    sdEx.initErrorHalt("SdFatSdioEX begin() failed");
  }
  // make sdEx the current volume.
  sdEx.chvol();

  Serial.println("Dump start!");
  startDump(&romSizeMb);
}

4. 결론

 ROM을 덤프할 수 있을 코드를 완성하였다. 다음은 실제 칩과 연결하여 ROM을 덤프할 차례이다.

5부에서 계속...

댓글 없음:

댓글 쓰기