게임 로컬 CLI를 위한 데스크톱 클라이언트
마비노기 모바일 PC에서 제공하는 로컬 CLI를 화면으로 사용하고 싶어 .NET WPF 앱을 만들었습니다. 매 행동을 LLM이 판단하게 하기보다, CLI 응답에 있는 상태를 읽어 조회와 채집·제작 흐름을 처리하도록 구성했습니다.
가장 신경 쓴 부분은 작업 계획을 처음부터 확정하지 않는 것이었습니다. CLI가 알려주는 현재 부족분을 확인하고 행동한 뒤, 다음 작업 전에 상태를 다시 읽도록 했습니다.
CLI 응답 표시
게임 설치 폴더를 지정하면 앱에서 CLI 프로세스를 실행하고 JSON 응답을 읽습니다. 한국어 인자는 인코딩을 맞춰 전달했습니다. 설치 경로와 최근 응답 같은 기기별 정보는 저장소 밖에서 관리했습니다.
화면에는 CLI가 반환하는 정보를 사용했습니다. 응답에 없는 캐릭터 정보나 전체 장비 목록을 임의로 채우지 않았습니다. 서버 이름과 캐릭터 이름처럼 비슷해 보이는 필드도 구분했습니다.
프로세스의 인자와 출력 읽기
실제 CliService.cs는 ProcessStartInfo로 CLI를 실행합니다. 명령을 셸 문자열 하나로 합치지 않고 ArgumentList에 넣고, 표준 출력과 오류를 따로 읽습니다. 다음은 해당 코드에서 실행에 필요한 부분을 정리한 발췌입니다.
var start = new ProcessStartInfo(cliPath) {
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
start.ArgumentList.Add(command);
if (!string.IsNullOrEmpty(body)) {
start.ArgumentList.Add(body.Any(c => c > 127)
? "base64:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(body))
: body);
}
using var process = Process.Start(start)
?? throw new InvalidOperationException("CLI 실행 실패");
var stdout = process.StandardOutput.ReadToEndAsync();
var stderr = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
var output = (await stdout).TrimStart('\uFEFF').Trim();
var error = (await stderr).Trim();cliPath, command, body는 호출부에서 전달한 값입니다. 원본 모듈의 using과 JSON 처리 일부는 생략했습니다. UTF-8 base64는 이 게임 CLI의 비 ASCII 인자 형식에 맞춘 것이며 모든 CLI에 필요한 방식은 아닙니다.
stdout과 stderr는 프로세스가 끝나기 전부터 읽기 시작합니다. 한쪽 출력을 모두 읽은 다음 다른 쪽을 읽으면 출력량에 따라 파이프가 막혀 기다릴 수 있기 때문입니다. 종료 뒤에는 출력의 BOM과 공백을 정리하고 JSON을 파싱합니다. 종료 코드, JSON 여부, 게임이 반환한 상태를 함께 읽어야 명령 성공을 판단할 수 있습니다.
모의 실행과 실제 게임 확인
저장소는 .NET 9 WPF 앱이며 ./build.ps1로 빌드합니다. 작업 순서를 확인하는 모의 CLI 테스트도 별도로 있습니다.
dotnet run --project tests/WorkflowSmoke/WorkflowSmoke.csproj -c Release이 명령은 게임 설치 없이 작업 순서를 확인하는 용도입니다. 실제 게임 조회는 연결을 준비한 뒤 별도의 --live-cli 옵션으로 구분합니다. 블로그 글을 보강하면서 실제 게임 행동을 새로 실행하지는 않았습니다.
실제 앱은 최근 제작 응답을 사용자 로컬 데이터 폴더에 저장합니다. “계획 완료” 표시와 게임 결과가 어긋날 때 앱이 어떤 응답을 보고 판단했는지 확인하기 위해서입니다. 이 파일에는 기기·게임 상태가 들어갈 수 있어 저장소의 테스트 fixture와 같은 자료로 다루지 않습니다.
현재 부족한 재료 계산
CLI 응답만으로 원하는 품목의 전체 레시피와 비용을 처음부터 확정할 수는 없었습니다. 그래서 현재 부족분을 확인하고 가능한 채집이나 중간품 작업을 수행한 뒤 다시 조회했습니다.
- 목표 품목의 현재 부족분을 확인합니다.
- 직접 얻을 재료와 선행 작업을 구분합니다.
- 가능한 작업을 수행합니다.
- 다음 행동 직전에 상태를 다시 읽습니다.
이 과정에서 자동으로 얻을 수 없는 재료가 부족하면 계획을 멈춥니다. 처음 조회한 값이 계속 유효하다고 가정하거나, 없는 재료와 슬롯 수를 추정해 다음 행동을 실행하지 않도록 했습니다.
읽기 전용 조회의 병렬 처리
스케줄이 실행 중이어도 조회 화면으로 이동할 수 있게 했습니다. 반면 충돌할 수 있는 게임 행동이나 설치 경로 변경은 제한했습니다.
독립적인 상태 조회는 병렬로 처리하고, 화면은 준비된 페이지를 한 번에 바꾸도록 했습니다. 이는 정보를 가져오는 시간을 줄이기 위한 변경이며 게임 행동을 모두 동시에 실행한다는 뜻은 아닙니다.
| 동작 | 처리 |
|---|---|
| 독립적인 상태 조회 | 병렬 호출 가능 |
| 제작·채집 | 상태를 다시 확인하며 순서대로 실행 |
| 작업 중 화면 탐색 | 읽기 전용 이동 허용 |
| 중지 | 진행 중 명령 종료 후 적용 |
작업 순서와 실제 응답 확인
모의 CLI로 확인한 작업 순서와 실제 게임 응답으로 확인한 범위는 따로 기록했습니다. 조회가 병렬로 잘 동작해도 제작과 채집의 상태 전환까지 같은 결과로 판단할 수는 없기 때문입니다.
앱의 범위는 CLI가 제공하는 기능 안에서 반복 조회와 조작을 줄이는 데 뒀습니다. 전체 게임 상태를 알고 모든 목표를 자동으로 처리하는 방식보다, 응답에서 확인할 수 있는 다음 행동을 수행하도록 구성했습니다.