# 스크립트 파일 포맷 분석

**날짜**: 2025-11-29
**프로젝트**: NeoRylClient

---

## 파일 타입

### 스크립트 엔진 파일 확장자

**ScriptEngine/BaseDef.h:6-8**:
```cpp
#define MACHINECODEFILEEXT  ".mcf"    // Machine Code File
#define INTCODEFILEEXT      ".imc"    // Intermediate Code
#define SCRIPTFILEEXT       ".dat"    // Game Script File
```

**파일 타입 설명**:
- **`.mcf`**: 컴파일된 바이너리 바이트코드 (암호화됨)
- **`.dat`**: 소스 코드 스크립트 (Flex/Bison 파서로 처리)
- **`.imc`**: 중간 코드 (컴파일 과정의 중간 산출물)
- **`.csf`**: Camera Script File (카메라 애니메이션)

**참고**: `.gsf` 파일은 코드베이스에서 사용되지 않음

---

## .mcf 파일 로딩 프로세스

### 1. 파일 타입 자동 감지

**ScriptEngine.cpp:22-56** - `_SE_Create()`:
```cpp
SCRIPT _SE_Create(const char* codeFile) {
    FILE* fp = fopen(codeFile, "r");
    if(fp == NULL) {
        ErrorMessage2("Cannot open file : %s", codeFile);
        return NULL;
    }

    CVirtualMachine* pVM = new CVirtualMachine;
    char szFilename[128];
    strcpy(szFilename, codeFile);
    strlwr(szFilename);

    if(strstr(szFilename, MACHINECODEFILEEXT) != 0) {
        // .mcf: 바이너리 머신 코드 직접 로드
        pVM->Create(codeFile);
    }
    else if(strstr(szFilename, SCRIPTFILEEXT) != 0) {
        // .dat: 소스 코드를 컴파일하여 실행
        CSyntaxTree SyntaxTree;
        CIntermediateCode IMCode;

        SyntaxTree.Create(codeFile);      // 파싱
        IMCode.Create(SyntaxTree);        // 중간 코드 생성
        pVM->Create(IMCode, *SyntaxTree.GetSymbolTable());
    }
    else {
        return NULL;
    }

    return pVM;
}
```

### 2. XOR 암호화 복호화

**VirtualMachine.cpp:69-78** - XOR 복호화 함수:
```cpp
void Data_XOR(char* pDataBuf, unsigned size, int keyValue) {
    int* pIntData = (int*)pDataBuf;
    int IntDataSize = size/sizeof(int);

    for(int i = 0; i < IntDataSize; i++) {
        pIntData[i] = pIntData[i] ^ keyValue;  // 4바이트 단위로 XOR
    }
}
```

**VirtualMachine.cpp:117-138** - .mcf 파일 로딩:
```cpp
void CVirtualMachine::Create(const char* szFilename) {
    ifstream file(szFilename, ios::binary | ios::in);

    if(!file.is_open())
        ErrorMessage2("파일을 찾을 수 없습니다. (At CVirtualMachine::Create) : %s", szFilename);

    // 1. 파일 크기 확인 및 전체 읽기
    file.seekg(0, ios_base::end);
    unsigned int filesize = (unsigned int)file.tellg();
    file.seekg(0, ios_base::beg);

    char* pBuf = new char[filesize];

    // 2. XOR 키 생성
    int xor_key_valueT = xor_key_value ^ 0x601f1ac4;

    // 3. 파일 읽기 및 복호화
    file.read(pBuf, filesize);
    Data_XOR(pBuf, filesize, xor_key_valueT);

    // 4. 메모리 버퍼에서 VM 생성
    Create(pBuf, filesize);

    delete[] pBuf;
    file.close();
}
```

**암호화 특징**:
- **XOR 암호화**: 간단한 대칭키 암호화
- **키 값**: `xor_key_value ^ 0x601f1ac4` (xor_key_value는 외부에서 정의)
- **단위**: 4바이트(int) 단위로 암호화/복호화
- **양방향**: 동일한 키로 암호화/복호화 가능 (XOR의 특성)

### 3. 바이너리 구조 파싱

**VirtualMachine.cpp:148-187** (주석 처리된 원래 구현):

#### 파일 구조:
```
+-------------------------+
| Relocation Table        | (가변 길이)
+-------------------------+
| GlobalVarBufferSize     | 4 bytes (int) - 전역 변수 영역 크기
+-------------------------+
| StringBufferSize        | 4 bytes (int) - 문자열 버퍼 크기
+-------------------------+
| CodeSize                | 4 bytes (int) - 코드 영역 크기
+-------------------------+
| String Buffer           | StringBufferSize bytes - 상수 문자열
+-------------------------+
| Code Buffer             | CodeSize bytes - 바이트코드
+-------------------------+
| FuncMapSize             | 4 bytes (long) - 함수 개수
+-------------------------+
| Function Entry 1        |
|   - FuncName (string)   | null-terminated
|   - FuncType (long)     | 4 bytes - 함수 타입
|   - FuncOffset (long)   | 4 bytes - 코드 내 오프셋
+-------------------------+
| Function Entry 2        |
|   ...                   |
+-------------------------+
| ...                     |
+-------------------------+
| SysVarOffsets[4]        | 16 bytes (4 * int) - 시스템 변수 오프셋
+-------------------------+
```

#### 파싱 코드:
```cpp
void CVirtualMachine::Create(const char* szFilename) {
    // Relocation Table 로드
    m_pRelocation->Create(file);

    // 버퍼 크기 정보 읽기
    int GlobalVarBufferSize, StringBufferSize, TotalBufferSize;
    file.read((char*)&GlobalVarBufferSize, sizeof(int));
    file.read((char*)&StringBufferSize, sizeof(int));
    file.read((char*)&m_iCodeSize, sizeof(int));

    // 전체 버퍼 할당
    TotalBufferSize = GlobalVarBufferSize + StringBufferSize + m_iCodeSize;
    m_pGlobalVars = m_pBuffer = new char[TotalBufferSize];
    m_pStringBuffer = ((byte*)m_pGlobalVars) + GlobalVarBufferSize;
    m_pCodeBuffer = ((byte*)m_pStringBuffer) + StringBufferSize;

    // 데이터 로드
    memset(m_pGlobalVars, 0, GlobalVarBufferSize);
    file.read((char*)m_pStringBuffer, StringBufferSize);
    file.read((char*)m_pCodeBuffer, m_iCodeSize);

    // 함수 맵 로드
    long FuncMapSize;
    file.read((char*)&FuncMapSize, sizeof(long));

    string str;
    char endChar;
    long type, offset;

    for(int i = 0; i < FuncMapSize; i++) {
        file >> str;
        file.read(&endChar, 1);
        file.read((char*)&type, sizeof(long));
        file.read((char*)&offset, sizeof(long));
        offset += long(m_pCodeBuffer);
        m_pFunctionMap->insert(FUNCMAP::value_type(str, FUNCINFO(type, (void*)offset)));
    }

    // 시스템 변수 오프셋 로드
    file.read((char*)m_pSysVarOffset, sizeof(int)*4);
    file.close();

    // 재배치 및 시스템 변수 설정
    m_pRelocation->Relocate(m_pGlobalVars, m_pStringBuffer, m_pCodeBuffer);
    m_bRelocated = true;
    SetSysVars();
}
```

---

## 스크립트 실행 프로세스

### 1. Script.mcf 로딩

**RYLClientMain.cpp:490-521**:
```cpp
const TCHAR* szScriptFileName = _T("Script.mcf");

// 언어별 스크립트 경로 생성
_sntprintf(szScriptPath, MAX_PATH,
    "%s%sLanguage\\%s\\%s",
    szDrive, szDir, szLanguage[m_iLanguage], szScriptFileName);

// 스크립트 로드 및 실행
if (!CRYLStringTable::Load(szScriptPath, szMacroPath)) {
    MessageBox(NULL, "Can't load Language Script.", "Program Error", MB_OK);
}
```

**경로 예시**:
- Debug: `./Language/ENG/Script.mcf`
- Release: `../../../Game/Language/KOR/Script.mcf`

### 2. C++ 함수 등록 및 실행

**RYLStringTable.cpp:61-76**:
```cpp
bool CRYLStringTable::Load(const char* strScriptFile, const char* strMacroFile) {
    m_dwStringCount = 0;

    // 1. 에러 메시지 핸들러 등록
    _SE_SetMessageFunction(ScriptErrorMessage);

    // 2. 스크립트 로드 (XOR 복호화 + 파싱)
    SCRIPT Script = _SE_Create(strScriptFile);

    if (!Script)
        return false;

    // 3. C++ 함수를 스크립트에서 호출 가능하도록 등록
    _SE_RegisterFunction(Script, AddString, T_VOID, "AddString", T_STRING, 0);
    _SE_RegisterFunction(Script, AddStringCount, T_VOID, "AddString", T_INT, T_STRING, 0);
    _SE_RegisterFunction(Script, RylNation, T_VOID, "RylNation", T_INT, T_INT, T_STRING, 0);

    // 4. 가상 머신에서 스크립트 실행
    _SE_Execute(Script);

    // 5. 정리
    _SE_Destroy(Script);

    return true;
}
```

### 3. 등록된 C++ 함수

**RYLStringTable.cpp:27-59**:

#### AddString
```cpp
static void AddString(const char* strMessage) {
    if (strMessage) {
        strcpy(CRYLStringTable::m_strString[CRYLStringTable::m_dwStringCount], strMessage);
        CRYLStringTable::m_dwStringCount++;
    }
}
```

#### AddStringCount
```cpp
static void AddStringCount(int nCount, const char* strMessage) {
    if (strMessage) {
        strcpy(CRYLStringTable::m_strString[nCount], strMessage);
        CRYLStringTable::m_dwStringCount = nCount + 1;
    }
}
```

#### RylNation
```cpp
static void RylNation(int nInternationalCode, int nServerType, const char* szRYLGameGuardCode) {
    CRYLStringTable::m_eInternationalCode = static_cast<GameRYL::ServiceNation>(nInternationalCode);
    CRYLStringTable::m_eServerType = static_cast<GameRYL::ServerType>(nServerType);

    _snprintf(CRYLStringTable::m_szGameGuardCode,
        CRYLStringTable::MAX_GAMEGUARD_CODE_LEN, szRYLGameGuardCode);

    // 하드코딩: 항상 GLOBAL/REGULAR로 설정
    CRYLStringTable::m_eInternationalCode = GameRYL::GLOBAL;
    CRYLStringTable::m_eServerType = GameRYL::SERVER_REGULAR;
    strcpy(CRYLStringTable::m_szGameGuardCode, "RYLYouxi");

    CRYLStringTable::m_szGameGuardCode[CRYLStringTable::MAX_GAMEGUARD_CODE_LEN - 1] = 0;
}
```

---

## 커스텀 스크립트 언어

### 컴파일 도구체인

**ScriptEngine 프로젝트**:
- **Lexer**: `string.l` (Flex 기반 토크나이저)
- **Parser**: `string.y` (Bison 기반 파서)
- **Compiler**: `Compiler.cpp` (구문 트리 생성)
- **Code Generator**: `IMCodeGen.cpp` (중간 코드 생성)
- **Virtual Machine**: `VirtualMachine.cpp` (바이트코드 실행)

### 컴파일 프로세스

```
.dat (소스 파일)
    ↓
[Flex Lexer] - string.l
    ↓
[Tokens]
    ↓
[Bison Parser] - string.y
    ↓
[Syntax Tree]
    ↓
[Intermediate Code Generator]
    ↓
[.imc 파일]
    ↓
[Code Generator]
    ↓
[.mcf 파일] (XOR 암호화)
```

### Script.mcf의 역할

**용도**:
- 게임 UI 문자열 테이블 초기화
- 국제화 코드 및 서버 타입 설정
- GameGuard 초기화 코드 설정
- 총 8192개의 문자열 슬롯 사용

**스크립트 예시** (추정):
```
RylNation(5, 1, "RYLYouxi");  // 국가코드, 서버타입, GG코드

AddString("Welcome to RYL");       // 인덱스 0
AddString("Login");                // 인덱스 1
AddString("Password");             // 인덱스 2
...
AddString(100, "Character Name");  // 특정 인덱스에 추가
```

---

## 보안 특성

### XOR 암호화

**장점**:
- 빠른 속도 (단순한 비트 연산)
- 구현이 간단
- 양방향 암호화 (동일 키로 암호화/복호화)

**단점**:
- **매우 약한 보안**: XOR 키만 알면 즉시 복호화 가능
- 패턴 분석에 취약
- 키 관리가 코드에 하드코딩됨

**키 값**:
```cpp
int xor_key_valueT = xor_key_value ^ 0x601f1ac4;
```
- `xor_key_value`: 외부에서 정의 (찾지 못함 - 다른 파일에 정의되어 있을 가능성)
- `0x601f1ac4`: 고정된 매직 넘버

### 목적

- **난독화**: 일반 사용자가 쉽게 수정하지 못하도록 방지
- **무결성**: 스크립트 파일 변조 감지는 **없음** (체크섬/해시 없음)
- **보안 수준**: Low - 역공학 방지용 난독화 수준

---

## 복호화 도구 작성 방법

### 1. .mcf 복호화기

```cpp
#include <fstream>
#include <iostream>

void DecryptMCF(const char* inputFile, const char* outputFile, int xorKey) {
    std::ifstream inFile(inputFile, std::ios::binary);
    std::ofstream outFile(outputFile, std::ios::binary);

    // 파일 크기 확인
    inFile.seekg(0, std::ios::end);
    size_t fileSize = inFile.tellg();
    inFile.seekg(0, std::ios::beg);

    // 버퍼 할당
    char* buffer = new char[fileSize];
    inFile.read(buffer, fileSize);

    // XOR 복호화
    int* intBuffer = (int*)buffer;
    int intCount = fileSize / sizeof(int);

    for(int i = 0; i < intCount; i++) {
        intBuffer[i] = intBuffer[i] ^ xorKey;
    }

    // 저장
    outFile.write(buffer, fileSize);

    delete[] buffer;
    inFile.close();
    outFile.close();
}

int main() {
    // xor_key_value를 찾아서 대입
    int xorKey = UNKNOWN_XOR_KEY ^ 0x601f1ac4;

    DecryptMCF("Script.mcf", "Script_decrypted.bin", xorKey);

    return 0;
}
```

### 2. XOR 키 찾기

**방법 1**: 디버거로 실행 중 `xor_key_value` 확인
- `CVirtualMachine::Create()` 함수에 브레이크포인트
- `xor_key_valueT` 값 확인

**방법 2**: 바이너리 패턴 분석
- .mcf 파일과 알려진 문자열 패턴 비교
- XOR 키 역산

**방법 3**: 메모리 덤프
- 게임 실행 중 메모리에서 복호화된 스크립트 덤프

---

## 관련 파일

### 주요 소스 파일
- `ScriptEngine/VirtualMachine.cpp` - VM 구현 및 .mcf 로더
- `ScriptEngine/ScriptEngine.cpp` - 스크립트 엔진 API
- `RYLClient/RYLClient/RYLStringTable.cpp` - Script.mcf 사용
- `ScriptEngine/BaseDef.h` - 파일 확장자 정의

### 컴파일러 도구
- `ScriptEngine/Compiler/` - 스크립트 컴파일러 프로젝트
- `ScriptEngine/string.l` - Flex lexer
- `ScriptEngine/string.y` - Bison parser

### 실행 파일
- `Game/Language/[언어]/Script.mcf` - 언어별 문자열 테이블

---

**작성일**: 2025-11-29
**프로젝트**: NeoRylClient
