◎ Part 1: 헤더 + 매크로 + 변환 함수들

#ifndef MAIN_H
#define MAIN_H

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <wincrypt.h>
#include <commdlg.h>
#include <wchar.h>

#pragma comment(lib, "advapi32.lib")
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
#pragma comment(linker, "/ENTRY:WinMainCRTStartup")

// Control IDs
#define IDC_INPUT_TEXT      101
#define IDC_OUTPUT_TEXT     102

// Conversion buttons
#define IDC_TEXT_TO_HEX     201
#define IDC_HEX_TO_TEXT     202
#define IDC_DEC_TO_HEX      203
#define IDC_HEX_TO_DEC      204
#define IDC_TEXT_TO_DEC     205
#define IDC_DEC_TO_TEXT     206
#define IDC_DEC_TO_OCTAL    207
#define IDC_OCTAL_TO_DEC    208
#define IDC_TEXT_TO_UTF7    209
#define IDC_UTF7_TO_TEXT    210
#define IDC_HEX_TO_UCS2     211
#define IDC_UCS2_TO_HEX     212
#define IDC_TEXT_TO_BINARY  213
#define IDC_BINARY_TO_TEXT  214
#define IDC_ESCAPE          215
#define IDC_UNESCAPE        216
#define IDC_ENCODE_HTML     217
#define IDC_DECODE_HTML     218
#define IDC_TEXT_TO_BASE64  219
#define IDC_BASE64_TO_TEXT  220
#define IDC_HEX_TO_BASE64   221
#define IDC_BASE64_TO_HEX   222
#define IDC_IP_TO_DEC       223
#define IDC_DEC_TO_IP       224
#define IDC_IP_TO_HEX       225
#define IDC_HEX_TO_IP       226
#define IDC_TEXT_TO_URL     227
#define IDC_URL_TO_TEXT     228

// Utility buttons
#define IDC_COPY_OUTPUT     301
#define IDC_COPY_TO_INPUT   302
#define IDC_CLEAR_ALL       303
#define IDC_FILE_SELECT     304

#define IDC_TEXT_A          401
#define IDC_TEXT_B          402
#define IDC_REPLACE_GO      403

// SHA256 (32 bytes = 64 hex chars)
#define SHA256_HASH_SIZE 32
#define SHA256_HEX_SIZE (SHA256_HASH_SIZE * 2 + 1) // 64 + '\0'

// SHA512 (64 bytes = 128 hex chars)
#define SHA512_HASH_SIZE 64
#define SHA512_HEX_SIZE (SHA512_HASH_SIZE * 2 + 1) // 128 + '\0'

// Buffer sizes
#define _CRT_SECURE_NO_WARNINGS
#define MAX_BUFFER 8192
#define BUF_SIZE 8192
#define FILE_BUF_SIZE 65536 // 64KB: 다중 선택 경로 저장을 위한 큰 버퍼


#define BASE64_DECODE_MAX_SIZE(n) (((n) / 4) * 3 + 1)
#define TEXT_TO_HEX_SIZE(n) (((n) == 0) ? 1 : (((n) * 3)))
#define HEX_TO_TEXT_MAX_SIZE(n) (((n) / 2) + 1)
#define TEXT_TO_DEC_SIZE(n) ((n) == 0 ? 1 : ((n) * 4 + 1))
#define DEC_TO_HEX_MAX_SIZE(count) ((count) * 9 + 1)
#define TEXT_TO_BINARY_SIZE(n) ((n) == 0 ? 1 : ((n) * 9))
#define BINARY_TO_TEXT_MAX_SIZE(bits) (((bits) / 8) + 1)
#define ESCAPE_STRING_MAX_SIZE(n) ((n) * 2 + 1)
#define ENCODE_HTML_MAX_SIZE(n) ((n) * 6 + 1)

// ============ 공통 헬퍼 함수들 ============

// Hex 문자를 숫자로 변환
static inline int hexCharToValue(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    return -1;
}

//
static inline size_t uintToStr(unsigned int val, char* buf) {
    if (!buf) return 0;
    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[11];  // 최대 10자리 + null
    size_t len = 0;

    while (val > 0) {
        temp[len++] = '0' + (val % 10);
        val /= 10;
    }

    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 숫자를 10진수 문자열로 변환
static inline size_t uintToDecimal(unsigned int val, char* buf) {
    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[11];
    size_t len = 0;

    while (val > 0) {
        temp[len++] = '0' + (val % 10);
        val /= 10;
    }

    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 숫자를 16진수 문자열로 변환
static inline size_t uintToHex(unsigned int val, char* buf) {
    static const char hexDigits[] = "0123456789ABCDEF";

    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[9];
    size_t len = 0;

    while (val > 0) {
        temp[len++] = hexDigits[val & 0x0F];
        val >>= 4;
    }

    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 안전한 2진수 파싱
static bool parseBinaryByte(const char* str, unsigned char* result) {
    if (!str || strlen(str) != 8) {
        return false;
    }

    // 모든 문자가 0 또는 1인지 확인
    for (int i = 0; i < 8; i++) {
        if (str[i] != '0' && str[i] != '1') {
            return false;
        }
    }

    char* endptr;
    errno = 0;
    unsigned long val = strtoul(str, &endptr, 2);

    if (errno != 0 || endptr != str + 8 || val > 255) {
        return false;
    }

    *result = (unsigned char)val;
    return true;
}

// UTF-8로 인코딩 (간단한 버전)
static size_t encodeUTF8(unsigned int codepoint, char* output) {
    if (codepoint <= 0x7F) {
        // 1바이트: 0xxxxxxx
        output[0] = (char)codepoint;
        return 1;
    }
    else if (codepoint <= 0x7FF) {
        // 2바이트: 110xxxxx 10xxxxxx
        output[0] = (char)(0xC0 | (codepoint >> 6));
        output[1] = (char)(0x80 | (codepoint & 0x3F));
        return 2;
    }
    else if (codepoint <= 0xFFFF) {
        // 3바이트: 1110xxxx 10xxxxxx 10xxxxxx
        output[0] = (char)(0xE0 | (codepoint >> 12));
        output[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
        output[2] = (char)(0x80 | (codepoint & 0x3F));
        return 3;
    }
    else if (codepoint <= 0x10FFFF) {
        // 4바이트: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        output[0] = (char)(0xF0 | (codepoint >> 18));
        output[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
        output[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
        output[3] = (char)(0x80 | (codepoint & 0x3F));
        return 4;
    }

    return 0;  // 잘못된 코드포인트
}

// Dec to 8진수(Octal)로 변환
static size_t uintToOctal(unsigned int num, char* output) {
    if (num == 0) {
        output[0] = '0';
        output[1] = '\0';
        return 1;
    }

    char buffer[12]; // 최대 11자리 + 널 문자
    int i = 11;
    buffer[i] = '\0';

    while (num > 0 && i > 0) {
        i--;
        buffer[i] = (num % 8) + '0';
        num /= 8;
    }

    size_t len = 11 - i;
    // 결과 복사
    for (size_t j = 0; j < len; j++) {
        output[j] = buffer[i + j];
    }
    output[len] = '\0';

    return len;
}

// 8진수(Octal)에서 변환
static size_t uintToDec(unsigned int num, char* output) {
    // 32비트 unsigned int는 최대 10자리 (4294967295) + 널 문자
    int len = snprintf(output, 11, "%u", num);
    if (len < 0 || len >= 11) {
        return 0; // 변환 실패 또는 버퍼 부족
    }
    return (size_t)len;
}

// text to UTF7로 변환
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static size_t encode_base64_block(const unsigned char* input, size_t inputLen,
    char* output, size_t outSize) {
    if (inputLen == 0 || inputLen > 3) {
        return 0;
    }

    size_t outPos = 0;
    unsigned int block = 0;

    // 바이트 조립
    for (size_t i = 0; i < inputLen; i++) {
        block = (block << 8) | (unsigned char)input[i];
    }

    // 패딩을 포함한 블록 생성
    size_t padding = 3 - inputLen;
    block <<= (8 * padding);

    // Base64 문자 수 계산
    size_t b64_char_count = (inputLen * 8 + 5) / 6;

    if (outSize < b64_char_count + 1) {
        return 0;
    }

    // 6비트씩 추출
    size_t total_bits = (inputLen + padding) * 8;
    for (size_t j = 0; j < b64_char_count; j++) {
        int shift = (int)(total_bits - (j + 1) * 6);  // 시프트는 int OK
        unsigned char index = (block >> shift) & 0x3F;
        output[outPos++] = base64_chars[index];
    }

    return outPos;
}

// ============ 해시 관련 함수들 ============

// 헬퍼 함수: 바이트 배열을 16진수 문자열로 변환 (소문자)
static void bytesToHex(const BYTE* bytes, size_t len, char* hexHash, size_t hexHashSize) {
    // 버퍼 크기 검증 (len * 2 + 1 필요)
    if (!bytes || !hexHash || hexHashSize < (len * 2 + 1)) {
        return;
    }

    for (size_t i = 0; i < len; i++) {
        sprintf_s(hexHash + (i * 2), hexHashSize - (i * 2), "%02x", bytes[i]);
    }
    hexHash[len * 2] = '\0'; // 명시적 널 종료
}

/**
 * @brief 파일의 SHA 해시를 계산하는 일반 함수.
 * @param filePath 파일 경로 (와이드 문자)
 * @param hexHash 결과 버퍼
 * @param hexHashSize 결과 버퍼 크기 (SHA256: 65, SHA512: 129 이상)
 * @param algId 사용할 해시 알고리즘 ID (CALG_SHA_256 또는 CALG_SHA_512)
 * @param expectedHashSize 예상되는 해시 출력 바이트 크기 (32 또는 64)
 * @return 성공 시 true, 실패 시 false
 */
static bool calculateFileHash(
    const wchar_t* filePath,
    char* hexHash,
    size_t hexHashSize,
    ALG_ID algId,
    DWORD expectedHashSize)
{
    BOOL success = FALSE;
    HCRYPTPROV hProv = 0;
    HCRYPTHASH hHash = 0;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    BYTE* fileBuf = NULL;
    DWORD bytesRead = 0;
    BYTE hashVal[SHA512_HASH_SIZE] = { 0 }; // 최대 크기 (SHA512)
    DWORD hashSize = expectedHashSize;

    // 1. 입력 검증
    if (!filePath || !hexHash || hexHashSize < (expectedHashSize * 2 + 1)) {
        return false;
    }

    // 결과 버퍼 초기화
    memset(hexHash, 0, hexHashSize);

    // 2. 파일 버퍼 동적 할당 (스택 오버플로우 방지)
    fileBuf = (BYTE*)malloc(BUF_SIZE);
    if (!fileBuf) {
        return false;
    }

    // 3. 파일 열기 (CreateFileW)
    hFile = CreateFileW(filePath, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        free(fileBuf);
        return false;
    }

    // 4. 암호화 서비스 공급자 획득 (CryptAcquireContextW)
    if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
        // PROV_RSA_AES 실패 시 PROV_RSA_FULL로 재시도
        if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
            goto cleanup;
        }
    }

    // 5. 해시 객체 생성 (CryptCreateHash)
    if (!CryptCreateHash(hProv, algId, 0, 0, &hHash)) {
        goto cleanup;
    }

    // 6. 파일 읽기 및 해시 계산 (CryptHashData)
    while (TRUE) {
        if (!ReadFile(hFile, fileBuf, BUF_SIZE, &bytesRead, NULL)) {
            // ReadFile 실패
            goto cleanup;
        }

        if (bytesRead == 0) {
            // 파일 끝 정상 도달
            break;
        }

        if (!CryptHashData(hHash, fileBuf, bytesRead, 0)) {
            goto cleanup;
        }
    }

    // 7. 해시값 획득 (CryptGetHashParam)
    if (!CryptGetHashParam(hHash, HP_HASHVAL, hashVal, &hashSize, 0)) {
        goto cleanup;
    }

    // 8. 해시 크기 검증 및 16진수 변환
    if (hashSize == expectedHashSize) {
        bytesToHex(hashVal, hashSize, hexHash, hexHashSize);
        success = TRUE;
    }

cleanup:
    if (hHash) CryptDestroyHash(hHash);
    if (hProv) CryptReleaseContext(hProv, 0);
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
    if (fileBuf) free(fileBuf);

    return success;
}

// SHA256 래퍼 함수
bool calculateFileHashSHA256(const wchar_t* filePath, char* hexHash, size_t hexHashSize) {
    return calculateFileHash(filePath, hexHash, hexHashSize, CALG_SHA_256, SHA256_HASH_SIZE);
}

// SHA512 래퍼 함수
bool calculateFileHashSHA512(const wchar_t* filePath, char* hexHash, size_t hexHashSize) {
    return calculateFileHash(filePath, hexHash, hexHashSize, CALG_SHA_512, SHA512_HASH_SIZE);
}

// --- 헬퍼 함수 1: Base64 문자에서 값 가져오기 ---

// Base64 문자(0-63)의 인덱스 값을 반환합니다.
static int base64_char_to_value(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;

    // UTF-7 Base64 블록 내에서는 패딩 문자 '='는 발생하지 않아야 합니다.
    return -1; // 유효하지 않은 Base64 문자
}

// --- 헬퍼 함수 2: UTF-7 Modified Base64 디코딩 ---

// Base64 문자열을 디코딩하여 바이트 스트림 (UTF-16 바이트)으로 변환합니다.
// inputLen: Base64 문자열의 길이 (예: 'ICE'는 3)
// outSize: 디코딩된 바이트를 저장할 버퍼 크기
static size_t decode_base64_block(const char* input, size_t inputLen, unsigned char* output, size_t outSize) {
    size_t outPos = 0;
    size_t i = 0;

    while (i < inputLen) {
        int val[4];
        int val_count = 0;

        // Base64 문자 4개(또는 끝까지) 읽고 6비트 값으로 변환
        for (int j = 0; j < 4 && i < inputLen; j++) {
            int current_val = base64_char_to_value(input[i++]);
            if (current_val != -1) {
                val[val_count++] = current_val;
            }
            else {
                // 유효하지 않은 Base64 문자는 무시하거나 오류 처리 가능. 여기서는 오류 처리 없이 진행.
            }
        }

        if (val_count == 0) break;

        // 6비트 4개를 묶어 24비트 블록 생성 (최대)
        unsigned int block = 0;
        if (val_count >= 1) block |= val[0] << 18;
        if (val_count >= 2) block |= val[1] << 12;
        if (val_count >= 3) block |= val[2] << 6;
        if (val_count >= 4) block |= val[3];

        // 디코딩되는 바이트 수 계산 (val_count * 6 / 8의 내림)
        int byte_count = (val_count * 3) / 4;

        // 1 바이트 (상위 8비트)
        if (byte_count >= 1) {
            if (outPos >= outSize) return 0; // 버퍼 부족
            output[outPos++] = (unsigned char)((block >> 16) & 0xFF);
        }

        // 2 바이트 (중간 8비트)
        if (byte_count >= 2) {
            if (outPos >= outSize) return 0;
            output[outPos++] = (unsigned char)((block >> 8) & 0xFF);
        }

        // 3 바이트 (하위 8비트)
        if (byte_count >= 3) {
            if (outPos >= outSize) return 0;
            output[outPos++] = (unsigned char)(block & 0xFF);
        }
    }

    return outPos;
}

#endif // MAIN_H

HWND hInputEdit, hOutputEdit;
HWND hTextA = NULL;
HWND hTextB = NULL;

 

1. #include 와 #define

 - 전처리기와 헤더들

#define WIN32_LEAN_AND_MEAN
  • #define은 “매크로 정의”입니다.
  • WIN32_LEAN_AND_MEAN을 정의하면 windows.h 안에서 자주 안 쓰는 오래된 API들을 빼고 포함해서, 컴파일 속도와 충돌 가능성을 줄여 줍니다.
#include <windows.h>
  • Windows API 함수와 자료형(예: HWND, HINSTANCE, CreateWindowEx 등)을 쓰기 위해 필요한 헤더입니다.
#include <stdio.h>
  • 표준 입출력 함수들(printf, snprintf 등)을 사용하기 위한 헤더입니다.
#include <string.h>
  • 문자열 관련 함수들(strlen, memcpy, strncmp 등)을 사용하기 위한 헤더입니다.
#include <stdbool.h>
  • Boolean 타입(bool, true, false)을 사용하기 위한 헤더입니다.
#include <stdint.h>
  • 고정 크기 정수 타입들(int8_t, uint16_t, int32_t, uint64_t 등)을 사용하기 위한 헤더입니다.
#include <stdlib.h>
  • malloc, free, atoi, strtol 같은 메모리/숫자 변환 함수를 사용하기 위한 헤더입니다.
#include <ctype.h>
  • isdigit, isxdigit 등의 문자 판별 함수를 사용하기 위한 헤더입니다.

 

 - 링커·컴파일러 옵션

#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
  • 링커에게 “이 프로그램은 윈도우 GUI 프로그램이다”라고 알려 줍니다.​ 그래서 콘솔 창 없이 실행되게 됩니다.
#pragma comment(linker, "/ENTRY:WinMainCRTStartup")
  • 링커에게 프로그램 시작 함수 이름을 지정합니다.
  • 여기서는 C 런타임 초기화 함수 WinMainCRTStartup을 엔트리로 쓰겠다는 의미이고, 그 안에서 결국 사용자가 만든 WinMain이 호출됩니다.

 

 - 컨트롤 ID 정의

#define IDC_INPUT_TEXT      101
#define IDC_OUTPUT_TEXT     102
  • 에디트 박스(입력/출력)에 붙일 ID 번호입니다.
  • 나중에 WM_COMMAND에서 LOWORD(wParam)을 읽어 “어떤 컨트롤인지” 구분할 때 사용합니다.
// Conversion buttons
#define IDC_TEXT_TO_HEX     201
#define IDC_HEX_TO_TEXT     202
#define IDC_DEC_TO_HEX      203
#define IDC_HEX_TO_DEC      204
#define IDC_TEXT_TO_DEC     205
#define IDC_DEC_TO_TEXT     206
#define IDC_DEC_TO_OCTAL    207
#define IDC_OCTAL_TO_DEC    208
#define IDC_TEXT_TO_UTF7    209
#define IDC_UTF7_TO_TEXT    210
#define IDC_HEX_TO_UCS2     211
#define IDC_UCS2_TO_HEX     212
#define IDC_TEXT_TO_BINARY  213
#define IDC_BINARY_TO_TEXT  214
#define IDC_ESCAPE          215
#define IDC_UNESCAPE        216
#define IDC_ENCODE_HTML     217
#define IDC_DECODE_HTML     218
#define IDC_TEXT_TO_BASE64  219
#define IDC_BASE64_TO_TEXT  220
#define IDC_HEX_TO_BASE64   221
#define IDC_BASE64_TO_HEX   222
  • 각각의 변환 기능 버튼에 대한 고유 ID입니다.
  • 예를 들어 "Text to Hex" 버튼을 만들 때 IDC_TEXT_TO_HEX를 붙여 두고, 나중에 switch (btnId)에서 case IDC_TEXT_TO_HEX:이면 textToHex() 함수를 호출하는 식으로 연결합니다.​
// Utility buttons
#define IDC_COPY_OUTPUT     226
#define IDC_COPY_TO_INPUT   227
#define IDC_CLEAR_ALL       228
  • 유틸리티 기능(출력 복사, 출력→입력 복사, 전체 지우기) 버튼들의 ID입니다.
  • 마찬가지로 WM_COMMAND에서 이 번호로 어떤 버튼인지 구분합니다.

 

 - 버퍼 크기 상수

#define MAX_BUFFER 8192
  • 이 프로그램에서 문자열 버퍼를 만들 때 공통으로 사용할 최대 크기입니다.
  • 예: char input[MAX_BUFFER]; 같이 사용해서, 한 번에 처리할 수 있는 최대 글자 수를 8192 바이트로 제한합니다.


2. 전역 변수

HWND hInputEdit, hOutputEdit;
  • HWND = "Handle to Window" : “윈도우(컨트롤)를 가리키는 핸들(번호)”입니다.​
  • 윈도우 핸들 : 입력/출력 텍스트박스를 어디서나 접근할 수 있도록 전역변수를 선언합니다.
  • hInputEdit : 입력용 에디트 박스, hOutputEdit : 출력용 에디트 박스를 가리킵니다.

 

+ Recent posts