moonst3ad
НЕ В СЕТИ
EN
Contractors Showdown: ExfilZone
// Contractors Showdown: ExfilZone

Как извлечь .uasset и .uexp из ExfilZone (09.08.2026 — патч 1.108.0)

Безопасно извлекаем данные ExfilZone из pakchunk0 и pakchunk1: патроны, броню, ресиверы, магазины, таблицы совместимости и кривые.

EXFILZONEPCVRUNREAL ENGINEDATA MINING

Проверено 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. Скачайте инструменты

Использованные версии:

  1. repak CLI v0.2.3 — индекс и выборочная распаковка PAK.
  2. UAssetGUI v1.1.0 — перевод структурированных ассетов в JSON.
  3. 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 читается специализированным бинарным парсером в сериализованном порядке:

  1. BulletCaliber и BulletAttribute;
  2. DamageOverDistance;
  3. PenetrationPowerOverDistance;
  4. MuzzleVelocity;
  5. BluntDamageScale и BleedingChance;
  6. множители урона прочности брони при пробитии и тупом уроне.

Время ключей кривой хранится в сантиметрах — для метров делите на 100. Скорость хранится в см/с — для м/с тоже делите на 100. Девять профилей пропускают сериализованный нулевой BluntDamageScale; простой поиск последовательности float сдвинет остальные поля.

Шаг 13. Что читать в остальных ассетах

  • Броня: raw export Default__<имя ассета>_C, кривые шанса пробития и урона, уровень, durability и ссылка на кривую износа.
  • Ресиверы: HeadDamageScale, DamageRangeCurve, FiringRate, legacy PenetrationPower/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 — материалы игры. Храните их локально для исследования и не распространяйте сами ассеты. Публикуйте методику, нормализованные таблицы и собственные выводы.

Источники инструментов