- Windows 11 24H2에서 Skimmer 수상비행기가 사라지거나 스폰 직후 플레이어가 비정상적으로 높은 하늘로 튀어 오르는 문제가 재현됐고, 원인은 OS가 아니라 게임 내부의 오래된 데이터 처리 버그였음
- Skimmer의
vehicles.ide 행에는 비행기에 필요한 휠 스케일 2개 값이 빠져 있었지만, CFileLoader::LoadVehicleObject는 sscanf 반환값을 확인하지 않아 초기화되지 않은 지역 변수를 그대로 사용함
- 예전 Windows 환경에서는 직전 차량 TopFun의
0.7 휠 스케일 값이 스택에 우연히 남아 Skimmer가 정상처럼 보였지만, Windows 11 24H2에서 LeaveCriticalSection의 스택 사용량이 달라지며 그 우연이 깨짐
- 잘못된 휠 스케일은 서스펜션 계산과 충돌 박스 Z 좌표를 오염시켰고, 생성 높이와 블레이드 속도 계산까지 전파되어 카메라 위치 이상, burn-in 효과, SilentPatch 환경의 루프 정지로 이어짐
- 해결은
vehicles.ide의 Skimmer 행에 -1, 0.7, 0.7, -1을 추가하거나 다음 SilentPatch 핫픽스를 적용하는 것이며, 입력 데이터 검증과 컴파일 경고 관리가 장기 호환성에 직접 영향을 줌
Windows 11 24H2에서 드러난 Skimmer 증상
- SilentPatch 이슈 트래커에 Windows 11 24H2 업데이트 이후 Skimmer 비행기가 게임에서 완전히 사라졌다는 보고가 올라옴
- 트레이너로도 스폰되지 않고, 원래 스폰 위치에서도 찾을 수 없었음
- 모드가 포함된 게임과 SilentPatch만 적용한 바닐라 복사본 모두에서 재현됨
- GTAForums에서도 2024년 11월부터 같은 문제가 보고됐고, 일부 사용자는 SilentPatch를 의심했지만 완전한 무모드 게임에서도 같은 현상이 나타남
- Windows 10 22H2와 Windows 11 23H2에서는 Skimmer가 정상 스폰됐고, Windows 11 24H2 사용자들은 같은 버그를 겪음
- 24H2 가상 머신에서 원격 디버깅한 결과, 다른 비행기와 보트는 정상이고 Skimmer만 사라지는 상태였음
비정상 고도와 끝나지 않는 블레이드 루프
- 스크립트로 Skimmer를 강제로 생성하고 CJ를 태우면 플레이어가
1.0287648030984853e+0031m, 약 10.3 nonillion 미터 높이로 튀어 오름
- SilentPatch가 설치된 경우 게임은 플레이어를 위로 날린 직후 루프에 빠져 멈춤
- SilentPatch가 없으면 게임은 멈추지 않지만, 카메라가 무한대에 가까운 위치로 이동할 때 생기는 유명한 burn-in 효과가 나타남
- 멈춘 위치는
CPlane::PreRender의 로터 블레이드 각도 정규화 루프였음
m_fBladeSpeed 값이 3.73340132e+29까지 커짐
6.2831855를 반복해서 빼도 부동소수점 표현상 값이 변하지 않아 루프가 끝나지 않음
- 블레이드 속도는 비행기 고도에 비례하는 값에서 파생되므로, Skimmer가 처음부터 비정상적으로 높은 위치에 생성됐다는 단서가 됨
충돌 박스를 오염시킨 서스펜션 계산
- 스크립트 생성 함수
CCarCtrl::CreateCarForScript는 전달받은 Z 좌표에 GetDistanceFromCentreOfMassToBaseOfModel 결과를 더함
- Skimmer의 충돌 박스를 확인하자
bbox.sup.z가 -4.30747210e+33처럼 말이 안 되는 값으로 오염돼 있었음
- 데이터 브레이크포인트로 추적한 결과, 초기 로딩 시점의 충돌 박스 값은 정상임
- 초기
bbox.sup.z는 -2.21952772였음
- 이후 차량이 처음 스폰될 때
SetupSuspensionLines가 서스펜션 높이를 반영하며 충돌 박스 Z 좌표를 갱신함
- 문제는 서스펜션 라인 계산에 들어가는 입력값 중 하나였음
- 계산에는
handling.cfg의 서스펜션 상한·하한과 vehicles.ide의 휠 스케일이 사용됨
- Skimmer의
handling.cfg 값은 다른 비행기와 크게 다르지 않았음
Skimmer의 짧은 vehicles.ide 행
- Skimmer의
vehicles.ide 정의는 다른 비행기보다 짧고, 마지막 4개 매개변수가 빠져 있음
- 누락된 값 중 2개가 앞·뒤 휠 스케일임
- 보트에는 이 값이 없어도 문제가 없지만, Skimmer는 비행기 중 유일하게 해당 매개변수를 생략함
- Skimmer는 Vice City에서 보트로 정의됐다가 San Andreas에서 비행기로 바뀌는 과정에서 새로 필요한 매개변수가 추가되지 않은 것으로 보임
- 누락된 매개변수를 다시 넣으면 Skimmer가 정상 동작함
sscanf 반환값을 확인하지 않은 로더
CFileLoader::LoadVehicleObject는 vehicles.ide 한 줄을 sscanf로 파싱하면서 모든 매개변수가 항상 존재한다고 가정함
- 이 함수는
sscanf의 반환값을 확인하지 않고, 마지막 매개변수 대부분에 기본값도 넣지 않음
wheelModelID는 초기화되지 않음
frontWheelScale, rearWheelScale도 초기화되지 않음
wheelUpgradeClass만 -1로 초기화돼 있음
- Skimmer처럼 값이 빠진 행에서는 휠 스케일 변수가 초기화되지 않은 상태로 남고, 그 값이 차량 데이터로 전파됨
- SilentPatch 수정은
sscanf 호출을 감싸 마지막 4개 값에 기본값을 제공하는 방식임
wheelModelID = -1
frontWheelSize = 0.7f
rearWheelSize = 0.7f
wheelUpgradeClass = -1
- 수정 커밋은 SilentPatch 저장소에 반영됨
20년 동안 숨어 있던 이유
- San Andreas는 정적으로 컴파일된 CRT를 사용하므로, Windows의 CRT 수준 핫픽스가
sscanf 동작을 바꾼 것은 아님
- Windows 10에서는 Skimmer 파싱 직전 지역 변수 위치에
0.7 값이 남아 있었음
- 이 값은 Skimmer 바로 앞에 정의된 TopFun의 휠 스케일과 일치함
- TopFun 행에는
-1, 0.7, 0.7, -1이 들어 있음
vehicles.ide는 순서대로 읽히며, 각 행마다 LoadVehicleObject가 호출됨
- Windows 10에서는
LoadVehicleObject 호출 사이에 해당 스택 위치가 덮어써지지 않아 Skimmer가 우연히 TopFun의 휠 스케일을 물려받음
- Windows 11 24H2에서는 다음 행을 읽는 과정에서
fgets 내부의 LeaveCriticalSection이 더 많은 스택 공간을 사용했고, 그 결과 남아 있던 값이 덮어써짐
Windows 11 24H2는 방아쇠였을 뿐
- 내부 WinAPI 함수가 스택을 사용하는 방식은 계약된 동작이 아니며, 사전 공지 없이 바뀔 수 있음
- Windows 11 24H2는 게임이 의존하던 우연한 스택 잔존 값을 없앴을 뿐, 실제 원인은 게임의 정의되지 않은 동작임
- Windows 10에서도 휠 스케일 바로 다음 지역 변수는 이미
LeaveCriticalSection에 의해 덮어써졌고, 게임은 몇 년 전에도 이 버그를 만날 수 있는 상태였음
- San Andreas는 Windows 98도 지원했기 때문에, 이 버그는 적어도 10여 개 Windows 버전과 여러 Wine 릴리스에서 우연히 드러나지 않았음
- 공식 1.01 PC 패치에서는 이 버그가 수정되지 않았지만, 원래 Xbox 릴리스에는 기본값
1.0을 넣는 수정이 들어가 있었음
- Steam 3.0, newsteam, RGL은 Xbox 코드 브랜치 기반이라 이 수정을 물려받음
- War Drum Studios의 Android, X360, PS3 릴리스와 Definitive Edition도 영향을 받음
SilentPatch가 0.7을 기본값으로 고른 이유
- SilentPatch는 Rockstar의 Xbox 수정처럼
1.0이 아니라 0.7을 기본 휠 스케일로 사용함
- 선택 근거는 세 가지임
- PC판에서 Skimmer는 지금까지 사실상 TopFun의 휠 스케일인
0.7로 동작해 왔음
- 물 위에 뜨는 다른 비보트 차량인 Sea Sparrow와 Vortex도 휠 스케일이
0.7임
- 게임 내 많은 자동차의 휠 스케일도
0.7임
직접 수정하는 방법
- 코드 수정은 다음 SilentPatch 핫픽스에 포함될 예정임
- 즉시 고치려면 San Andreas 디렉터리의
data\vehicles.ide를 메모장으로 열고 460, skimmer로 시작하는 행을 교체하면 됨
- 교체할 행은 다음과 같음
460, skimmer, skimmer, plane, SEAPLANE, SKIMMER, null, ignore, 5, 0, 0, -1, 0.7, 0.7, -1
오래된 게임 호환성이 남긴 교훈
- 이 문제는 San Andreas의 단순한 버그였고, 해당 함수는 원래부터 올바르게 동작할 수 없는 코드였음
- 내부 구현의 스택 레이아웃 변화도, 버그가 있는 애플리케이션이 특정 동작에 우연히 의존하면 호환성 문제로 이어질 수 있음
- 비슷한 사례로 Windows 10에서 깨졌던 Bully: Scholarship Edition도 잘못된 가정에 의존하다가 OS 변화로 문제가 드러남
- San Andreas의 근본 문제는 불완전한 설정 행을 걸러내지 못한 입력 데이터 검증 부재였음
- 이 코드는 원래 컴파일 경고를 냈을 가능성이 높으며, 경고를 무시하거나 비활성화하면 장기간 숨어 있던 버그가 실제 사용자 문제로 나타날 수 있음