브라우저에서 동작하는 바이너리 에디터를 개발한 이야기
바이너리 에디터란 무엇인가?
바이너리 에디터는 파일을 16진수나 ASCII 표기로 직접 편집할 수 있는 도구다. 일반 텍스트 에디터가 “문자열”을 편집 대상으로 삼는 것과 달리, 바이너리 에디터는 파일의 “날것의 바이트열”에 접근해 원하는 위치의 비트나 바이트를 직접 변경할 수 있다.
소프트웨어 분석, 데이터 복구, 프로토콜 조사에서 빠질 수 없는 도구이며 GUI 애플리케이션으로는 HxD
, Binary Ninja
등이 유명하다. 하지만 이번 목표는 **“브라우저에서만 동작하는 단일 파일 바이너리 에디터”**였다.
구현 방침
- 100% 클라이언트 사이드(외부 전송 없음)
FileReader
API로 로컬 파일 읽기- 왼쪽에는 16진수(Hex), 오른쪽에는 ASCII를 표시
- 셀을 클릭하면 편집 모드로 들어가 값을 바로 덮어쓴다
- 편집한 셀은 빨간색으로 하이라이트
- HEX 편집 / ASCII 편집 모드를 전환 가능
- 편집 결과를 다시 파일로 다운로드할 수 있도록 하기
실제로 막혔던 지점과 수정
1. 편집 영역의 테두리가 어긋난 문제
초기 구현에서는 편집 대상 셀에 노란색 테두리를 표시하면 약간 중심이 맞지 않아 어색했다. CSS의 line-height
와 flex
배치 영향으로 문자 표시 기준점과 테두리 기준점이 어긋나 생기는 현상이었다. 모노스페이스 폰트를 전제로 했지만 브라우저 렌더링 과정에서 폰트마다 베이스라인이 달라질 수 있어 결국 위나 아래로 쏠렸다.
vertical-align: middle
을 강제하고 display: inline-block
으로 높이를 맞추어 해결했다.
.hex-cell.editing {
outline: 2px solid yellow;
vertical-align: middle;
}
2. ESC 키를 누르면 문자가 입력되는 문제
초기 구현에서는 편집 모드에서 ESC를 누르면 셀에 “EE” 같은 문자가 입력되었다.
keydown
이벤트를 가로채지 않아 브라우저가 눌린 키의 코드를 그대로 편집 처리에 넘긴 것이 원인이었다. 기본 입력 처리는 09와 AF만을 예상했지만, Escape
키에도 키 코드가 할당되어 있고(구형 구현에서는 keyCode=27
, 최신 사양에서는 key="Escape"
) 이 입력이 그대로 처리 루트에 실려 "E"
로 해석되면서 "EE"
가 써지는 동작이 나왔다.
해결책은 간단했다. keydown
이벤트에서 Escape
를 명시적으로 잡아 셀 편집을 취소하는 처리로 바꾸면 된다.
document.addEventListener("keydown", e => {
if (e.key === "Escape") {
cancelEdit();
e.preventDefault(); // 기본 입력 경로로 흐르지 않게 막는다
}
});
이제 ESC는 “편집 해제”로만 동작하고 셀에 문자가 입력되지 않는다.
3. 다시 불러올 때 빨간색이 남는 문제
한 번 파일을 읽어 편집한 뒤 다른 파일을 열면 이전의 빨간색 하이라이트가 남아 있는 버그가 있었다.
원인은 편집한 셀을 추적하는 배열과 상태를 파일 읽기 시점에 초기화하지 않았기 때문이었다. 표시 영역만 지워도 내부 “변경 플래그”가 남아 있었기 때문에 새 파일에도 빨간색 상태가 이어졌다.
수정 방법은 파일을 읽은 직후 clearModifiedState()
를 호출해 변경 플래그를 완전히 리셋하도록 하는 것이었다.
4. HEX/ASCII 모드 전환
처음 구현에서는 HEX와 ASCII를 동시에 편집할 수 있도록 했더니 어떤 입력이 우선인지 헷갈리고, 동일 바이트를 두 경로에서 바꾸면서 충돌이 발생했다. ASCII로 입력하면 즉시 HEX 표기가 덮어쓰기되지만, 입력 중에는 일시적으로 불일치한 상태가 화면에 보이기도 했다.
이 혼란을 막기 위해 모드를 셀렉트 박스로 명시적으로 고르도록 바꾸고 기본값을 HEX 편집으로 통일했다. 이렇게 하니 사용자 입장에서는 “지금은 HEX를 입력하고 있다”는 인지가 확실해지고 동기화 로직도 단순해졌다.
코드 일부
실제 편집 처리는 아래와 같이 구현했다.
function applyEdit(offset, newValue) {
if (mode === "hex") {
buffer[offset] = parseInt(newValue, 16);
} else {
buffer[offset] = newValue.charCodeAt(0);
}
markModified(offset);
render();
}
markModified(offset)
은 빨간색 하이라이트용으로 원래 값과 다를 때 클래스를 부여하는 단순한 구현이다.
마무리
이번 브라우저 버전 바이너리 에디터는 오프라인에서도 작동하며, 보안 측면에서도 안전한 완전 클라이언트 사이드 도구가 되었다.
- 간단한 바이너리 수정을 하고 싶을 때
- 전용 앱을 설치할 수 없는 환경에서 조사할 때
- 교육·학습 용도로 “바이트열”을 직관적으로 이해하고 싶을 때
이런 상황에서 활용할 수 있다.