Проверено 09.08.2026 на Contractors Showdown: ExfilZone 1.108.0 (
oculus-5.5.3-release-1.108.0-v76.0-CL-0). Метод только читает PAK и складывает копии в отдельную папку. Не запускайтеpackи не подменяйте файлы игры.
Это расширенная версия методики, по которой собран наш отчёт по боеприпасам, броне и оружию. Первый вариант гайда охватывал патроны и броню из pakchunk1. Теперь в него добавлены ресиверы, магазины и оружейные кривые из pakchunk0, а также таблицы каталога и совместимости из pakchunk1.
Что мы получим
После выполнения шагов в отдельной рабочей папке появятся:
- 81 пара профилей патронов;
- 38 пар шлемов, 36 пар бронежилетов и 7 глобальных броневых кривых;
- 61 пара ресиверов и 90 пар магазинов;
- 6 таблиц каталога, зависимостей и совместимости;
- 32 оружейные DOD/velocity-кривые, найденные по ссылкам ресиверов;
- пары
.uasset+.uexpи JSON-копии структурированных ассетов.
На проверенном снимке это 351 пара, или 702 исходных файла. После патча число может измениться — сравнивайте не только итог, но и сами пути.
.uasset содержит заголовок, таблицы имён и описание объекта. Основной сериализованный payload часто вынесен в соседний .uexp, поэтому хранить нужно оба файла с одинаковым именем.
Шаг 1. Найдите папку игры
Стандартный путь Meta Horizon выглядит так:
C:\Program Files\Meta Horizon\Software\Software\caveman-game-production-contractors-showdown-hrjitv\Contractors_Showdown
У старой установки Oculus начало пути может быть C:\Program Files\Oculus\Software\Software\....
Правильная корневая папка содержит:
Content\Paks\pakchunk0-Windows.pak
Content\Paks\pakchunk1-Windows.pak
Binaries\Win64\Contractors_Showdown.exe
pakchunk1 хранит Warfare-данные: патроны, броню и таблицы. pakchunk0 хранит оружейный слой: ресиверы, магазины и связанные кривые.
Шаг 2. Создайте отдельную рабочую папку
Не распаковывайте данные поверх игры:
D:\ExfilZoneExtract\
├─ tools\
├─ extracted\pakchunk0\
└─ extracted\pakchunk1\
Шаг 3. Скачайте инструменты
Использованные версии:
- repak CLI v0.2.3 — индекс и выборочная распаковка PAK.
- UAssetGUI v1.1.0 — перевод структурированных ассетов в JSON.
oo2core_9_win64.dllиз WorkingRobot/OodleUE — поддержка Oodle-сжатия.
Положите три файла в D:\ExfilZoneExtract\tools. DLL должна находиться рядом с repak.exe.
| Файл | SHA-256 проверенной версии |
|---|---|
| ZIP repak v0.2.3 | 6720d602144d75df477a99d5bedb6ea780997546afc335901d4937cafeaa73fa |
repak.exe |
fcd538e5994b9bb833622d425ae346f4e0692f02d4b0025114a559f9b6286022 |
UAssetGUI.exe |
b7d75c0893f1a60e565853ae638bc21f2416cd12c2d9d854e297abb87ceb3263 |
oo2core_9_win64.dll |
6f5d41a7892ea6b2db420f2458dad2f84a63901c9a93ce9497337b16c195f457 |
Get-FileHash "D:\ExfilZoneExtract\tools\repak.exe" -Algorithm SHA256
Get-FileHash "D:\ExfilZoneExtract\tools\UAssetGUI.exe" -Algorithm SHA256
Get-FileHash "D:\ExfilZoneExtract\tools\oo2core_9_win64.dll" -Algorithm SHA256
Не отключайте антивирус ради файла с неизвестным происхождением.
Шаг 4. Задайте пути и проверьте оба PAK
$GameRoot = "C:\Program Files\Meta Horizon\Software\Software\caveman-game-production-contractors-showdown-hrjitv\Contractors_Showdown"
$Work = "D:\ExfilZoneExtract"
$Repak = Join-Path $Work "tools\repak.exe"
$UAssetGUI = Join-Path $Work "tools\UAssetGUI.exe"
$PakWeapons = Join-Path $GameRoot "Content\Paks\pakchunk0-Windows.pak"
$PakWarfare = Join-Path $GameRoot "Content\Paks\pakchunk1-Windows.pak"
$OutWeapons = Join-Path $Work "extracted\pakchunk0"
$OutWarfare = Join-Path $Work "extracted\pakchunk1"
foreach ($Path in @($Repak, $UAssetGUI, $PakWeapons, $PakWarfare)) {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "Файл не найден: $Path"
}
}
New-Item -ItemType Directory -Force -Path $OutWeapons, $OutWarfare | Out-Null
Исходные PAK здесь только читаются.
Шаг 5. Получите два независимых индекса
$IndexWeapons = & $Repak list $PakWeapons
if ($LASTEXITCODE -ne 0) { throw "Не удалось прочитать pakchunk0" }
$IndexWarfare = & $Repak list $PakWarfare
if ($LASTEXITCODE -ne 0) { throw "Не удалось прочитать pakchunk1" }
$IndexWeapons | Set-Content -Encoding utf8 (Join-Path $Work "pakchunk0-index.txt")
$IndexWarfare | Set-Content -Encoding utf8 (Join-Path $Work "pakchunk1-index.txt")
"pakchunk0: $($IndexWeapons.Count) записей"
"pakchunk1: $($IndexWarfare.Count) записей"
На снимке 1.108.0 было 42 214 записей в pakchunk0 и 27 203 в pakchunk1. Пути внутри PAK используют /, даже в Windows.
Шаг 6. Проверьте извлечение одной пары
$Asset = "Contractors_Showdown/Content/Blueprints/GameModes/Warfare/Ammo/BulletProfiles/12Gauge/12Gauge_DamageType_Buckshot7.uasset"
$Payload = $Asset -replace '\.uasset$', '.uexp'
& $Repak unpack -q -f -o $OutWarfare -i $Asset -i $Payload $PakWarfare
if ($LASTEXITCODE -ne 0) { throw "Пробное извлечение завершилось с ошибкой" }
Test-Path -LiteralPath (Join-Path $OutWarfare $Asset)
Test-Path -LiteralPath (Join-Path $OutWarfare $Payload)
Обе проверки должны вернуть True.
Шаг 7. Выберите данные из pakchunk1
$AmmoPrefix = "Contractors_Showdown/Content/Blueprints/GameModes/Warfare/Ammo/BulletProfiles/"
$HelmetPattern = '^Contractors_Showdown/Content/Blueprints/GameModes/Warfare/Helmet/(?:.*/)?WarfareHelmet_[^/]+\.(uasset|uexp)$'
$VestPattern = '^Contractors_Showdown/Content/Blueprints/GameModes/Warfare/TecVest/(?:.*/)?WarfareTecVest_[^/]+\.(uasset|uexp)$'
$ArmorCurvePattern = '/(PenetrationChangeCurveHelmet|PenetrationChangeCurveVest|AntiPenetrationDurabilityScalarCurveLv[2-6])\.(uasset|uexp)$'
$TablePattern = '/(AmmoBoxTable|WarfarePropDependencyTable|GunSmithBlackDataTable|GunSmithTypeDataTable|GunSmithGunPartDataTable|TagCheckDataTable)\.(uasset|uexp)$'
$IncludesWarfare = @($IndexWarfare | Where-Object {
($_ -like "$AmmoPrefix*.uasset") -or
($_ -like "$AmmoPrefix*.uexp") -or
($_ -match $HelmetPattern) -or
($_ -match $VestPattern) -or
($_ -match $ArmorCurvePattern) -or
($_ -match $TablePattern)
} | Sort-Object -Unique)
$IncludesWarfare | Set-Content -Encoding utf8 (Join-Path $Work "selected-pakchunk1.txt")
"pakchunk1: выбрано $($IncludesWarfare.Count) файлов"
Для 1.108.0 результат — 336 файлов: прежние 324 файла патронов и брони плюс 12 файлов шести таблиц.
Шаг 8. Выберите ресиверы и магазины из pakchunk0
$ReceiverPattern = '/AdvanceGun/.+/GunParts?/LowerReceiver_[^/]+\.(uasset|uexp)$'
$MagazinePattern = '/AdvanceGun/.+/GunParts?/Mag_[^/]+\.(uasset|uexp)$'
$IncludesWeapons = @($IndexWeapons | Where-Object {
(($_ -match $ReceiverPattern) -and ($_ -notlike '*/GrabInteractionActor/*')) -or
($_ -match $MagazinePattern)
} | Sort-Object -Unique)
$IncludesWeapons | Set-Content -Encoding utf8 (Join-Path $Work "selected-pakchunk0-base.txt")
"pakchunk0, без кривых: выбрано $($IncludesWeapons.Count) файлов"
Для 1.108.0 это 302 файла: 61 пара ресиверов и 90 пар магазинов.
Шаг 9. Распакуйте выборки пакетами
function Expand-PakSelection {
param(
[string[]]$Entries,
[string]$Pak,
[string]$Out
)
for ($Start = 0; $Start -lt $Entries.Count; $Start += 28) {
$Last = [Math]::Min($Start + 27, $Entries.Count - 1)
$Args = @("unpack", "-q", "-f", "-o", $Out)
foreach ($Entry in @($Entries[$Start..$Last])) {
$Args += @("-i", $Entry)
}
$Args += $Pak
& $Repak @Args
if ($LASTEXITCODE -ne 0) { throw "Ошибка repak с индекса $Start" }
}
}
Expand-PakSelection -Entries $IncludesWarfare -Pak $PakWarfare -Out $OutWarfare
Expand-PakSelection -Entries $IncludesWeapons -Pak $PakWeapons -Out $OutWeapons
Шаг 10. Проверьте пары и создайте JSON
$MissingPairs = Get-ChildItem -LiteralPath $OutWeapons, $OutWarfare -Recurse -File -Filter *.uasset | ForEach-Object {
$Pair = [System.IO.Path]::ChangeExtension($_.FullName, ".uexp")
if (-not (Test-Path -LiteralPath $Pair -PathType Leaf)) { $_.FullName }
}
if ($MissingPairs) {
$MissingPairs
throw "Найдены .uasset без соседнего .uexp"
}
$JsonCandidates = Get-ChildItem -LiteralPath $OutWeapons, $OutWarfare -Recurse -File -Filter *.uasset | Where-Object {
$_.BaseName -notmatch '_DamageType_'
}
foreach ($Source in $JsonCandidates) {
$Json = [System.IO.Path]::ChangeExtension($Source.FullName, ".json")
& $UAssetGUI tojson $Source.FullName $Json VER_UE5_4
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $Json)) {
throw "Не удалось преобразовать: $($Source.FullName)"
}
}
JSON сохраняется рядом с исходной парой и не изменяет игру.
Шаг 11. Найдите связанные оружейные кривые
Ресиверы ссылаются на внешние CurveFloat: оружейный falloff (DamageRangeCurve) и масштабирование скорости (BulletVelocityScalingCurve). Сначала читаются JSON ресиверов, затем из pakchunk0 извлекаются найденные пакеты кривых. Дополнительно учитываются два fallback-ассета:
Contractors_Showdown/Content/Core/VRInteractables/ZomboyGunSystem/AI/Curves/Pistol_AOD.uasset
Contractors_Showdown/Content/Core/VRInteractables/ZomboyGunSystem/AI/Curves/Sniper_AOD_Warfare.uasset
При ручном исследовании можно собрать имена импортов CurveFloat так:
$CurveNames = @(
Get-ChildItem -LiteralPath $OutWeapons -Recurse -File -Filter "LowerReceiver_*.json" | ForEach-Object {
$Document = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
@($Document.Imports) | Where-Object { $_.ClassName -eq "CurveFloat" } | ForEach-Object { $_.ObjectName }
}
"Pistol_AOD"
"Sniper_AOD_Warfare"
) | Sort-Object -Unique
$CurveIncludes = @($IndexWeapons | Where-Object {
$_ -match '\.(uasset|uexp)$' -and
$CurveNames -contains [System.IO.Path]::GetFileNameWithoutExtension($_)
} | Sort-Object -Unique)
$CurveIncludes | Set-Content -Encoding utf8 (Join-Path $Work "selected-weapon-curves.txt")
Expand-PakSelection -Entries $CurveIncludes -Pak $PakWeapons -Out $OutWeapons
foreach ($CurveAsset in Get-ChildItem -LiteralPath $OutWeapons -Recurse -File -Filter *.uasset | Where-Object {
$CurveNames -contains $_.BaseName
}) {
$CurveJson = [System.IO.Path]::ChangeExtension($CurveAsset.FullName, ".json")
& $UAssetGUI tojson $CurveAsset.FullName $CurveJson VER_UE5_4
if ($LASTEXITCODE -ne 0) { throw "Не удалось преобразовать кривую: $($CurveAsset.FullName)" }
}
Такой ручной поиск может взять дополнительные CurveFloat. Наш анализатор сужает список до полей ресивера и на проверенном снимке получил 32 кривые и 262 контрольные точки.
Шаг 12. Почему патроны читаются иначе
Профили патронов — unversioned class default objects типа ZomboyAdvancedBulletDamageType. Без полной схемы UAssetGUI не всегда правильно подписывает их значения, поэтому .uexp читается специализированным бинарным парсером в сериализованном порядке:
BulletCaliberиBulletAttribute;DamageOverDistance;PenetrationPowerOverDistance;MuzzleVelocity;BluntDamageScaleиBleedingChance;- множители урона прочности брони при пробитии и тупом уроне.
Время ключей кривой хранится в сантиметрах — для метров делите на 100. Скорость хранится в см/с — для м/с тоже делите на 100. Девять профилей пропускают сериализованный нулевой BluntDamageScale; простой поиск последовательности float сдвинет остальные поля.
Шаг 13. Что читать в остальных ассетах
- Броня: raw export
Default__<имя ассета>_C, кривые шанса пробития и урона, уровень, durability и ссылка на кривую износа. - Ресиверы:
HeadDamageScale,DamageRangeCurve,FiringRate, legacyPenetrationPower/DamageType, ergonomics, velocity/drop и параметры дробовика. - Магазины:
ClipSize; если override отсутствует, значение из имени или каталога нужно помечать как derived, а не как точное. TagCheckDataTable: каталог деталей и требования слотов/тегов.WarfarePropDependencyTable: зависимости и bullet → gun-generator связи.- Кривые: все ключи и тип интерполяции, а не только первая и последняя точки.
Advanced bullet profiles и legacy-поля ресивера нельзя автоматически перемножать под видом подтверждённой формулы итогового урона: порядок runtime-композиции пока не установлен.
Карта источников
| Данные | PAK | Путь или правило |
|---|---|---|
| Патроны | pakchunk1 |
Warfare/Ammo/BulletProfiles/<caliber>/* |
| Шлемы и жилеты | pakchunk1 |
WarfareHelmet_*, WarfareTecVest_* |
| Броневые кривые | pakchunk1 |
PenetrationChangeCurve*, AntiPenetrationDurabilityScalarCurveLv2..Lv6 |
| Каталог и совместимость | pakchunk1 |
шесть таблиц из шага 7 |
| Ресиверы | pakchunk0 |
AdvanceGun/**/GunParts/LowerReceiver_* |
| Магазины | pakchunk0 |
AdvanceGun/**/GunParts/Mag_* |
| Оружейные кривые | pakchunk0 |
внешние CurveFloat, на которые ссылаются ресиверы |
| Нативные имена | EXE | Binaries/Win64/Contractors_Showdown.exe |
Частые ошибки
| Симптом | Причина и решение |
|---|---|
| repak сообщает об Oodle | Проверьте oo2core_9_win64.dll рядом с repak.exe. |
Есть .uasset, но UAssetGUI падает |
Проверьте одноимённый .uexp и параметр VER_UE5_4. |
| Нет ресиверов или магазинов | Вы, вероятно, ищете их в pakchunk1; они находятся в pakchunk0. |
| Нет таблиц Warfare | Таблицы находятся в pakchunk1, а не рядом с ресиверами. |
| Кривых больше 32 | Ручной поиск собрал все импорты CurveFloat; сузьте их до ссылок DOD/velocity ресиверов. |
| Поле отсутствует в JSON | Blueprint может наследовать значение; отсутствие не означает ноль. |
| Значения патронов неполные | Для unversioned-профилей нужна схема либо специализированный бинарный парсер. |
Как повторять после патча
Не используйте старые индексы. Для каждого нового снимка сохраните:
- версию и SHA-256
Contractors_Showdown.exe; - SHA-256 обоих PAK;
pakchunk0-index.txtиpakchunk1-index.txt;- три файла
selected-*.txt; - нормализованные CSV и fingerprints извлечённых ассетов.
Сначала сравнивайте списки и хеши, затем параметры. Изменённый fingerprint без распознанного параметра — потенциально новое поле, а не повод игнорировать ассет.
Извлечённые .uasset и .uexp — материалы игры. Храните их локально для исследования и не распространяйте сами ассеты. Публикуйте методику, нормализованные таблицы и собственные выводы.