Repository: WolvenKit/CP77Tools Branch: main Commit: 7171965963c7 Files: 36 Total size: 109.3 KB Directory structure: gitextract_ckm5538u/ ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── dotnet-core.yml │ └── nightly.yml ├── .gitignore ├── BUILD.md ├── CHANGELOG.txt ├── CP77.MSTests/ │ ├── ArchiveTests.cs │ ├── CP77.MSTests.csproj │ ├── Cr2wUnitTest.cs │ ├── FNV1A64Tests.cs │ ├── GameUnitTest.cs │ └── appsettings.json ├── CP77Tools/ │ ├── CP77Tools.csproj │ ├── Commands/ │ │ ├── CR2WCommand.cs │ │ ├── DumpCommand.cs │ │ ├── ExportCommand.cs │ │ ├── HashCommand.cs │ │ ├── OodleCommand.cs │ │ ├── PackCommand.cs │ │ ├── RebuildCommand.cs │ │ ├── UnbundleCommand.cs │ │ └── UncookCommand.cs │ ├── Extensions/ │ │ └── CommandLineExtensions.cs │ ├── Program.cs │ └── Tasks/ │ ├── Cr2wTask.cs │ ├── DumpTask.cs │ ├── ExportTask.cs │ ├── HashTask.cs │ ├── OodleTask.cs │ ├── PackTask.cs │ ├── RebuildTask.cs │ ├── UnbundleTask.cs │ └── UncookTask.cs ├── CP77Tools.sln ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Declare files to normalize crlf, in case people don't have core.autocrlf set. *.conf text eol=crlf *.config text eol=crlf *.cs text eol=crlf *.csproj text eol=crlf *.csv text eol=crlf *.editorconfig text eol=crlf *.gitattributes text eol=crlf *.gitignore text eol=crlf *.gitmodules text eol=crlf *.manifest text eol=crlf *.md text eol=crlf *.resx text eol=crlf *.settings text eol=crlf *.sln text eol=crlf *.txt text eol=crlf *.xml text eol=crlf *.yml text eol=crlf # Explicitly declare files you do not want to be treated as text *.bmp binary *.dll binary *.exe binary *.ico binary *.jpg binary *.md2 binary *.png binary *.rtf binary *.svg -text binary *.tga binary *.xbm binary *.zip binary *.lib filter=lfs diff=lfs merge=lfs -text ================================================ FILE: .github/workflows/dotnet-core.yml ================================================ name: .NET Core on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.100 - name: Clean run: dotnet clean ./CP77Tools.sln --configuration Release && dotnet nuget locals all --clear - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Upload a Build Artifact uses: actions/upload-artifact@v2.2.1 with: name: artifact path: ./CP77Tools/bin/Release/net5.0-windows - uses: papeloto/action-zip@v1 with: files: ./CP77Tools/bin/Release/net5.0-windows dest: release.zip - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: commit-${{ github.sha }} draft: true - name: Upload Release Asset id: upload-release-asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ./release.zip asset_name: release.zip asset_content_type: application/zip ================================================ FILE: .github/workflows/nightly.yml ================================================ name: Nightly on: push: branches: [ nightly ] pull_request: branches: [ nightly ] workflow_dispatch: jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.100 - name: Clean run: dotnet clean ./CP77Tools.sln --configuration Release && dotnet nuget locals all --clear - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Upload a Build Artifact uses: actions/upload-artifact@v2.2.1 with: name: artifact path: ./CP77Tools/bin/Release/net5.0 - uses: papeloto/action-zip@v1 with: files: ./CP77Tools/bin/Release/net5.0 dest: nightly.zip - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: commit-${{ github.sha }} draft: true prerelease: true - name: Upload Nightly Asset id: upload-release-asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ./nightly.zip asset_name: nightly.zip asset_content_type: application/zip ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf #rider .idea CP77Tools.sln.DotSettings.user #macOS .DS_Store #obj *obj/ *bin/ *.vs/ *.suo *.user *.pubxml CP77Tools/Properties/launchSettings.json CP77Tools/Resources/archivehashes.csv #include !texconv.exe !liboodle.dylib ================================================ FILE: BUILD.md ================================================ # Building CP77Tools ## Windows ### Requirements * Ensure that the .NET 5.0 SDK is installed. Use `dotnet --list-sdks` to check if it is already installed, if not, you can get it from here: https://dotnet.microsoft.com/download/dotnet/5.0 * Clone or Download the contents of the repo * Open PowerShell within the root folder (where `CP77Tools.sln` is) and run `dotnet build --configuration Release` * The compiled project can be found at `CP77Tools\bin\Release\net5.0\` ## Linux ??? ================================================ FILE: CHANGELOG.txt ================================================ # Changelog v1.1.0.2 Implemented - automatically copy oodle lib from game folder Fixed - fixed a bug where uncooking would not extract properly v1.1 Implemented - Uncook support for more extensions - extracting from missing hash list - deprecate archive command: split to unbundle, uncook - add export command Fixed - update archivehashes.zip - fixed vertical flip - fixed uppercase uncook extensions - code refactoring - bugfixes ================================================ FILE: CP77.MSTests/ArchiveTests.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; using System.Threading.Tasks; using Catel.IoC; using WolvenKit.Common.Services; using CP77.CR2W; using CP77.CR2W.Archive; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using WolvenKit.Common; namespace CP77.MSTests { [TestClass] public class ArchiveTests : GameUnitTest { [TestMethod] public void Test_Unbundle() { } [TestMethod] public void Test_Uncook() { } private void test_archive(string extension = null) { var resultDir = Path.Combine(Environment.CurrentDirectory, TestResultsDirectory); Directory.CreateDirectory(resultDir); var success = true; List archives; } } } ================================================ FILE: CP77.MSTests/CP77.MSTests.csproj ================================================ net5.0-windows false all runtime; build; native; contentfiles; analyzers; buildtransitive PreserveNewest ================================================ FILE: CP77.MSTests/Cr2wUnitTest.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; using System.Threading.Tasks; using Catel.IoC; using WolvenKit.Common.Services; using CP77.CR2W; using CP77.CR2W.Archive; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using WolvenKit.Common; namespace CP77.MSTests { [TestClass] public class Cr2WUnitTest : GameUnitTest { [ClassInitialize] public static void SetupClass(TestContext context) { Setup(context); } #region test methods [TestMethod] public void Test_All_Extensions() { Test_Extension(); } [TestMethod] public void Test_Can_Read_acousticdata() { Test_Extension(".acousticdata"); } [TestMethod] public void Test_Can_Read_actionanimdb() { Test_Extension(".actionanimdb"); } [TestMethod] public void Test_Can_Read_aiarch() { Test_Extension(".aiarch"); } [TestMethod] public void Test_Can_Read_animgraph() { Test_Extension(".animgraph"); } [TestMethod] public void Test_Can_Read_anims() { Test_Extension(".anims"); } [TestMethod] public void Test_Can_Read_app() { Test_Extension(".app"); } [TestMethod] public void Test_Can_Read_archetypes() { Test_Extension(".archetypes"); } [TestMethod] public void Test_Can_Read_areas() { Test_Extension(".areas"); } [TestMethod] public void Test_Can_Read_audio_metadata() { Test_Extension(".audio_metadata"); } [TestMethod] public void Test_Can_Read_audiovehcurveset() { Test_Extension(".audiovehcurveset"); } [TestMethod] public void Test_Can_Read_behavior() { Test_Extension(".behavior"); } [TestMethod] public void Test_Can_Read_bikecurveset() { Test_Extension(".bikecurveset"); } [TestMethod] public void Test_Can_Read_bin() { Test_Extension(".bin"); } [TestMethod] public void Test_Can_Read_bk2() { Test_Extension(".bk2"); } [TestMethod] public void Test_Can_Read_bnk() { Test_Extension(".bnk"); } [TestMethod] public void Test_Can_Read_camcurveset() { Test_Extension(".camcurveset"); } [TestMethod] public void Test_Can_Read_cfoliage() { Test_Extension(".cfoliage"); } [TestMethod] public void Test_Can_Read_charcustpreset() { Test_Extension(".charcustpreset"); } [TestMethod] public void Test_Can_Read_cminimap() { Test_Extension(".cminimap"); } [TestMethod] public void Test_Can_Read_community() { Test_Extension(".community"); } [TestMethod] public void Test_Can_Read_conversations() { Test_Extension(".conversations"); } [TestMethod] public void Test_Can_Read_cooked_mlsetup() { Test_Extension(".cooked_mlsetup"); } [TestMethod] public void Test_Can_Read_cookedanims() { Test_Extension(".cookedanims"); } [TestMethod] public void Test_Can_Read_cookedapp() { Test_Extension(".cookedapp"); } [TestMethod] public void Test_Can_Read_credits() { Test_Extension(".credits"); } [TestMethod] public void Test_Can_Read_csv() { Test_Extension(".csv"); } [TestMethod] public void Test_Can_Read_cubemap() { Test_Extension(".cubemap"); } [TestMethod] public void Test_Can_Read_curveset() { Test_Extension(".curveset"); } [TestMethod] public void Test_Can_Read_dat() { Test_Extension(".dat"); } [TestMethod] public void Test_Can_Read_devices() { Test_Extension(".devices"); } [TestMethod] public void Test_Can_Read_dtex() { Test_Extension(".dtex"); } [TestMethod] public void Test_Can_Read_effect() { Test_Extension(".effect"); } [TestMethod] public void Test_Can_Read_ent() { Test_Extension(".ent"); } [TestMethod] public void Test_Can_Read_env() { Test_Extension(".env"); } [TestMethod] public void Test_Can_Read_envparam() { Test_Extension(".envparam"); } [TestMethod] public void Test_Can_Read_envprobe() { Test_Extension(".envprobe"); } [TestMethod] public void Test_Can_Read_es() { Test_Extension(".es"); } [TestMethod] public void Test_Can_Read_facialcustom() { Test_Extension(".facialcustom"); } [TestMethod] public void Test_Can_Read_facialsetup() { Test_Extension(".facialsetup"); } [TestMethod] public void Test_Can_Read_fb2tl() { Test_Extension(".fb2tl"); } [TestMethod] public void Test_Can_Read_fnt() { Test_Extension(".fnt"); } [TestMethod] public void Test_Can_Read_folbrush() { Test_Extension(".folbrush"); } [TestMethod] public void Test_Can_Read_foldest() { Test_Extension(".foldest"); } [TestMethod] public void Test_Can_Read_fp() { Test_Extension(".fp"); } [TestMethod] public void Test_Can_Read_gamedef() { Test_Extension(".gamedef"); } [TestMethod] public void Test_Can_Read_garmentlayerparams() { Test_Extension(".garmentlayerparams"); } [TestMethod] public void Test_Can_Read_genericanimdb() { Test_Extension(".genericanimdb"); } [TestMethod] public void Test_Can_Read_geometry_cache() { Test_Extension(".geometry_cache"); } [TestMethod] public void Test_Can_Read_gidata() { Test_Extension(".gidata"); } [TestMethod] public void Test_Can_Read_gradient() { Test_Extension(".gradient"); } [TestMethod] public void Test_Can_Read_hitrepresentation() { Test_Extension(".hitrepresentation"); } [TestMethod] public void Test_Can_Read_hp() { Test_Extension(".hp"); } [TestMethod] public void Test_Can_Read_ies() { Test_Extension(".ies"); } [TestMethod] public void Test_Can_Read_inkanim() { Test_Extension(".inkanim"); } [TestMethod] public void Test_Can_Read_inkatlas() { Test_Extension(".inkatlas"); } [TestMethod] public void Test_Can_Read_inkcharcustomization() { Test_Extension(".inkcharcustomization"); } [TestMethod] public void Test_Can_Read_inkfontfamily() { Test_Extension(".inkfontfamily"); } [TestMethod] public void Test_Can_Read_inkfullscreencomposition() { Test_Extension(".inkfullscreencomposition"); } [TestMethod] public void Test_Can_Read_inkgamesettings() { Test_Extension(".inkgamesettings"); } [TestMethod] public void Test_Can_Read_inkhud() { Test_Extension(".inkhud"); } [TestMethod] public void Test_Can_Read_inklayers() { Test_Extension(".inklayers"); } [TestMethod] public void Test_Can_Read_inkmenu() { Test_Extension(".inkmenu"); } [TestMethod] public void Test_Can_Read_inkshapecollection() { Test_Extension(".inkshapecollection"); } [TestMethod] public void Test_Can_Read_inkstyle() { Test_Extension(".inkstyle"); } [TestMethod] public void Test_Can_Read_inktypography() { Test_Extension(".inktypography"); } [TestMethod] public void Test_Can_Read_inkwidget() { Test_Extension(".inkwidget"); } [TestMethod] public void Test_Can_Read_interaction() { Test_Extension(".interaction"); } [TestMethod] public void Test_Can_Read_journal() { Test_Extension(".journal"); } [TestMethod] public void Test_Can_Read_journaldesc() { Test_Extension(".journaldesc"); } [TestMethod] public void Test_Can_Read_json() { Test_Extension(".json"); } [TestMethod] public void Test_Can_Read_lane_connections() { Test_Extension(".lane_connections"); } [TestMethod] public void Test_Can_Read_lane_polygons() { Test_Extension(".lane_polygons"); } [TestMethod] public void Test_Can_Read_lane_spots() { Test_Extension(".lane_spots"); } [TestMethod] public void Test_Can_Read_lights() { Test_Extension(".lights"); } [TestMethod] public void Test_Can_Read_lipmap() { Test_Extension(".lipmap"); } [TestMethod] public void Test_Can_Read_location() { Test_Extension(".location"); } [TestMethod] public void Test_Can_Read_locopaths() { Test_Extension(".locopaths"); } [TestMethod] public void Test_Can_Read_loot() { Test_Extension(".loot"); } [TestMethod] public void Test_Can_Read_mappins() { Test_Extension(".mappins"); } [TestMethod] public void Test_Can_Read_mesh() { Test_Extension(".mesh"); } [TestMethod] public void Test_Can_Read_mi() { Test_Extension(".mi"); } [TestMethod] public void Test_Can_Read_mlmask() { Test_Extension(".mlmask"); } [TestMethod] public void Test_Can_Read_mlsetup() { Test_Extension(".mlsetup"); } [TestMethod] public void Test_Can_Read_mltemplate() { Test_Extension(".mltemplate"); } [TestMethod] public void Test_Can_Read_morphtarget() { Test_Extension(".morphtarget"); } [TestMethod] public void Test_Can_Read_mt() { Test_Extension(".mt"); } [TestMethod] public void Test_Can_Read_navmesh() { Test_Extension(".navmesh"); } [TestMethod] public void Test_Can_Read_null_areas() { Test_Extension(".null_areas"); } [TestMethod] public void Test_Can_Read_opusinfo() { Test_Extension(".opusinfo"); } [TestMethod] public void Test_Can_Read_opuspak() { Test_Extension(".opuspak"); } [TestMethod] public void Test_Can_Read_particle() { Test_Extension(".particle"); } [TestMethod] public void Test_Can_Read_phys() { Test_Extension(".phys"); } [TestMethod] public void Test_Can_Read_physicalscene() { Test_Extension(".physicalscene"); } [TestMethod] public void Test_Can_Read_physmatlib() { Test_Extension(".physmatlib"); } [TestMethod] public void Test_Can_Read_poimappins() { Test_Extension(".poimappins"); } [TestMethod] public void Test_Can_Read_psrep() { Test_Extension(".psrep"); } [TestMethod] public void Test_Can_Read_quest() { Test_Extension(".quest"); } [TestMethod] public void Test_Can_Read_questphase() { Test_Extension(".questphase"); } [TestMethod] public void Test_Can_Read_regionset() { Test_Extension(".regionset"); } [TestMethod] public void Test_Can_Read_remt() { Test_Extension(".remt"); } [TestMethod] public void Test_Can_Read_reslist() { Test_Extension(".reslist"); } [TestMethod] public void Test_Can_Read_rig() { Test_Extension(".rig"); } [TestMethod] public void Test_Can_Read_scene() { Test_Extension(".scene"); } [TestMethod] public void Test_Can_Read_scenerid() { Test_Extension(".scenerid"); } [TestMethod] public void Test_Can_Read_scenesversions() { Test_Extension(".scenesversions"); } [TestMethod] public void Test_Can_Read_smartobject() { Test_Extension(".smartobject"); } [TestMethod] public void Test_Can_Read_smartobjects() { Test_Extension(".smartobjects"); } [TestMethod] public void Test_Can_Read_sp() { Test_Extension(".sp"); } [TestMethod] public void Test_Can_Read_spatial_representation() { Test_Extension(".spatial_representation"); } [TestMethod] public void Test_Can_Read_streamingquerydata() { Test_Extension(".streamingquerydata"); } [TestMethod] public void Test_Can_Read_streamingsector() { Test_Extension(".streamingsector"); } [TestMethod] public void Test_Can_Read_streamingsector_inplace() { Test_Extension(".streamingsector_inplace"); } [TestMethod] public void Test_Can_Read_streamingworld() { Test_Extension(".streamingworld"); } [TestMethod] public void Test_Can_Read_terrainsetup() { Test_Extension(".terrainsetup"); } [TestMethod] public void Test_Can_Read_texarray() { Test_Extension(".texarray"); } [TestMethod] public void Test_Can_Read_traffic_collisions() { Test_Extension(".traffic_collisions"); } [TestMethod] public void Test_Can_Read_traffic_persistent() { Test_Extension(".traffic_persistent"); } [TestMethod] public void Test_Can_Read_voicetags() { Test_Extension(".voicetags"); } [TestMethod] public void Test_Can_Read_w2mesh() { Test_Extension(".w2mesh"); } [TestMethod] public void Test_Can_Read_w2mi() { Test_Extension(".w2mi"); } [TestMethod] public void Test_Can_Read_wem() { Test_Extension(".wem"); } [TestMethod] public void Test_Can_Read_workspot() { Test_Extension(".workspot"); } [TestMethod] public void Test_Can_Read_xbm() { Test_Extension(".xbm"); } [TestMethod] public void Test_Can_Read_xcube() { Test_Extension(".xcube"); } #endregion private void Test_Extension(string extension = null) { var resultDir = Path.Combine(Environment.CurrentDirectory, TestResultsDirectory); Directory.CreateDirectory(resultDir); var success = true; List files; if (string.IsNullOrEmpty(extension)) files = GroupedFiles.Keys.ToList(); else files = GroupedFiles.Keys.Where(k => k.Equals(extension)).ToList(); var sb = new StringBuilder(); Parallel.ForEach(files, ext => { var results = Read_Archive_Items(GroupedFiles[ext]); var successCount = results.Count(r => r.Success); var totalCount = GroupedFiles[ext].Count; sb.AppendLine($"{ext} -> Successful Reads: {successCount} / {totalCount} ({(int)(((double)successCount / (double)totalCount) * 100)}%)"); if (success && results.Any(r => !r.Success)) success = false; if (WriteToFile) { if (results.Any(r => !r.Success)) { var resultPath = Path.Combine(resultDir, $"{ext[1..]}.csv"); var csv = TestResultAsCsv(results.Where(r => !r.Success)); File.WriteAllText(resultPath, csv); } } }); var logPath = Path.Combine(resultDir, $"logfile_{(string.IsNullOrEmpty(extension) ? string.Empty : $"{extension[1..]}_")}{DateTime.Now:yyyyMMddHHmmss}.log"); File.WriteAllText(logPath, sb.ToString()); Console.WriteLine(sb.ToString()); if (!success) Assert.Fail(); } private IEnumerable Read_Archive_Items(List files) { var results = new List(); foreach (var file in files) try { var (fileBytes, bufferBytes) = (file.Archive as Archive).GetFileData(file.NameHash64, false); using var ms = new MemoryStream(fileBytes); using var br = new BinaryReader(ms); var c = new CR2WFile(); var readResult = c.Read(br); switch (readResult) { case EFileReadErrorCodes.NoCr2w: results.Add(new TestResult { ArchiveItem = file, Success = true, Result = TestResult.ResultType.NoCr2W }); break; case EFileReadErrorCodes.UnsupportedVersion: results.Add(new TestResult { ArchiveItem = file, Success = false, Result = TestResult.ResultType.UnsupportedVersion, Message = $"Unsupported Version ({c.GetFileHeader().version})" }); break; default: var hasAdditionalBytes = c.additionalCr2WFileBytes != null && c.additionalCr2WFileBytes.Any(); results.Add(new TestResult { ArchiveItem = file, Success = !hasAdditionalBytes, Result = hasAdditionalBytes ? TestResult.ResultType.HasAdditionalBytes : TestResult.ResultType.NoError, Message = hasAdditionalBytes ? $"Additional Bytes: {c.additionalCr2WFileBytes.Length}" : null, AdditionalBytes = hasAdditionalBytes ? c.additionalCr2WFileBytes.Length : 0 }); break; } } catch (Exception e) { results.Add(new TestResult { ArchiveItem = file, Success = false, Result = TestResult.ResultType.RuntimeException, ExceptionType = e.GetType(), Message = e.Message }); } return results; } private string TestResultAsCsv(IEnumerable results) { var sb = new StringBuilder(); sb.AppendLine( $@"{nameof(ArchiveItem.NameHash64)},{nameof(ArchiveItem.FileName)},{nameof(TestResult.Result)},{nameof(TestResult.Success)},{nameof(TestResult.AdditionalBytes)},{nameof(TestResult.ExceptionType)},{nameof(TestResult.Message)}"); foreach (var r in results) { sb.AppendLine( $"{r.ArchiveItem.NameHash64},{r.ArchiveItem.FileName},{r.Result},{r.Success},{r.AdditionalBytes},{r.ExceptionType?.FullName},{r.Message}"); } return sb.ToString(); } private class TestResult { public enum ResultType { NoCr2W, UnsupportedVersion, RuntimeException, HasAdditionalBytes, NoError } public ArchiveItem ArchiveItem { get; set; } [JsonConverter(typeof(StringEnumConverter))] public ResultType Result { get; set; } public int AdditionalBytes { get; set; } public bool Success { get; set; } public Type ExceptionType { get; set; } public string Message { get; set; } } } } ================================================ FILE: CP77.MSTests/FNV1A64Tests.cs ================================================ using System; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using WolvenKit.Common.FNV1A; namespace CP77.MSTests { [TestClass] public class FNV1A64Tests { [TestMethod] [DataRow("", 0xcbf29ce484222325UL)] [DataRow("a", 0xaf63dc4c8601ec8cUL)] [DataRow("foobar", 0x85944171f73967e8UL)] [DataRow("\0", 0xaf63bd4c8601b7dfUL)] [DataRow("a\0", 0x089be207b544f1e4UL)] [DataRow("foobar\0", 0x34531ca7168b8f38UL)] [DataRow("hi", 0x08ba5f07b55ec3daUL)] [DataRow("hi\0", 0x337354193006cb6eUL)] [DataRow("hello", 0xa430d84680aabd0bUL)] [DataRow("hello\0", 0xa9bc8acca21f39b1UL)] [DataRow("127.0.0.1", 0xaabafe7104d914beUL)] [DataRow("127.0.0.1\0", 0xf4d3180b3cde3edaUL)] [DataRow("127.0.0.2", 0xaabafd7104d9130bUL)] [DataRow("127.0.0.2\0", 0xf4cfb20b3cdb5bb1UL)] [DataRow("127.0.0.3", 0xaabafc7104d91158UL)] [DataRow("127.0.0.3\0", 0xf4cc4c0b3cd87888UL)] [DataRow("feedfacedeadbeef", 0xcac54572bb1a6fc8UL)] [DataRow("feedfacedeadbeef\0", 0xa7a4c9f3edebf0d8UL)] [DataRow("line 1\nline 2\nline 3", 0x7829851fac17b143UL)] public void TestFNV1a64(string test, ulong result) { // Assert.AreEqual(FNV1A64HashAlgorithm.HashString(test), result); Assert.AreEqual(FNV1A64HashAlgorithm.HashString(test), result); } [TestMethod] [DataRow("", 0xaf63bd4c8601b7dfUL)] [DataRow("a", 0x089be207b544f1e4UL)] [DataRow("hi", 0x337354193006cb6eUL)] [DataRow("hello", 0xa9bc8acca21f39b1UL)] [DataRow("foobar", 0x34531ca7168b8f38UL)] [DataRow("feedfacedeadbeef", 0xa7a4c9f3edebf0d8UL)] public void TestFNV1a64_NullEnded(string test, ulong result) { Assert.AreEqual(FNV1A64HashAlgorithm.HashString(test, Encoding.ASCII, true), result); } [TestMethod] [DataRow(new byte[] {0xff, 0x00, 0x00, 0x01}, 0x6961196491cc682dUL)] [DataRow(new byte[] {0x01, 0x00, 0x00, 0xff}, 0xad2bb1774799dfe9UL)] [DataRow(new byte[] {0xff, 0x00, 0x00, 0x02}, 0x6961166491cc6314UL)] [DataRow(new byte[] {0x02, 0x00, 0x00, 0xff}, 0x8d1bb3904a3b1236UL)] [DataRow(new byte[] {0xff, 0x00, 0x00, 0x03}, 0x6961176491cc64c7UL)] [DataRow(new byte[] {0x03, 0x00, 0x00, 0xff}, 0xed205d87f40434c7UL)] [DataRow(new byte[] {0xff, 0x00, 0x00, 0x04}, 0x6961146491cc5faeUL)] [DataRow(new byte[] {0x04, 0x00, 0x00, 0xff}, 0xcd3baf5e44f8ad9cUL)] [DataRow(new byte[] {0x40, 0x51, 0x4e, 0x44}, 0xe3b36596127cd6d8UL)] [DataRow(new byte[] {0x44, 0x4e, 0x51, 0x40}, 0xf77f1072c8e8a646UL)] [DataRow(new byte[] {0x40, 0x51, 0x4e, 0x4a}, 0xe3b36396127cd372UL)] [DataRow(new byte[] {0x4a, 0x4e, 0x51, 0x40}, 0x6067dce9932ad458UL)] [DataRow(new byte[] {0x40, 0x51, 0x4e, 0x54}, 0xe3b37596127cf208UL)] [DataRow(new byte[] {0x54, 0x4e, 0x51, 0x40}, 0x4b7b10fa9fe83936UL)] public void TestFNV1a64_ByteSpan(byte[] test, ulong result) { Assert.AreEqual(FNV1A64HashAlgorithm.HashReadOnlySpan(test), result); } [TestMethod] [DataRow("\xff\x00\x00\x01", 0x6961196491cc682dUL)] [DataRow("\x01\x00\x00\xff", 0xad2bb1774799dfe9UL)] [DataRow("\xff\x00\x00\x02", 0x6961166491cc6314UL)] [DataRow("\x02\x00\x00\xff", 0x8d1bb3904a3b1236UL)] [DataRow("\xff\x00\x00\x03", 0x6961176491cc64c7UL)] [DataRow("\x03\x00\x00\xff", 0xed205d87f40434c7UL)] [DataRow("\xff\x00\x00\x04", 0x6961146491cc5faeUL)] [DataRow("\x04\x00\x00\xff", 0xcd3baf5e44f8ad9cUL)] [DataRow("\x40\x51\x4e\x44", 0xe3b36596127cd6d8UL)] [DataRow("\x44\x4e\x51\x40", 0xf77f1072c8e8a646UL)] [DataRow("\x40\x51\x4e\x4a", 0xe3b36396127cd372UL)] [DataRow("\x4a\x4e\x51\x40", 0x6067dce9932ad458UL)] [DataRow("\x40\x51\x4e\x54", 0xe3b37596127cf208UL)] [DataRow("\x54\x4e\x51\x40", 0x4b7b10fa9fe83936UL)] [DataRow("", 0xcbf29ce484222325UL)] [DataRow("a", 0xaf63dc4c8601ec8cUL)] [DataRow("foobar", 0x85944171f73967e8UL)] [DataRow("\0", 0xaf63bd4c8601b7dfUL)] [DataRow("a\0", 0x089be207b544f1e4UL)] [DataRow("foobar\0", 0x34531ca7168b8f38UL)] [DataRow("hi", 0x08ba5f07b55ec3daUL)] [DataRow("hi\0", 0x337354193006cb6eUL)] [DataRow("hello", 0xa430d84680aabd0bUL)] [DataRow("hello\0", 0xa9bc8acca21f39b1UL)] [DataRow("127.0.0.1", 0xaabafe7104d914beUL)] [DataRow("127.0.0.1\0", 0xf4d3180b3cde3edaUL)] [DataRow("127.0.0.2", 0xaabafd7104d9130bUL)] [DataRow("127.0.0.2\0", 0xf4cfb20b3cdb5bb1UL)] [DataRow("127.0.0.3", 0xaabafc7104d91158UL)] [DataRow("127.0.0.3\0", 0xf4cc4c0b3cd87888UL)] [DataRow("feedfacedeadbeef", 0xcac54572bb1a6fc8UL)] [DataRow("feedfacedeadbeef\0", 0xa7a4c9f3edebf0d8UL)] [DataRow("line 1\nline 2\nline 3", 0x7829851fac17b143UL)] public void TestFNV1a64_CharSpan(string test, ulong result) { Assert.AreEqual(FNV1A64HashAlgorithm.HashReadOnlySpan(test.AsSpan()), result); } } } ================================================ FILE: CP77.MSTests/GameUnitTest.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Catel.IoC; using CP77.CR2W.Archive; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using WolvenKit.Common.Services; namespace CP77.MSTests { [TestClass] public class GameUnitTest { private const string GameDirectorySetting = "GameDirectory"; private const string WriteToFileSetting = "WriteToFile"; private static ArchiveManager bm; internal static Dictionary> GroupedFiles; private static IConfigurationRoot _config; internal const string TestResultsDirectory = "_CR2WTestResults"; private static string _gameDirectoryPath; internal static bool WriteToFile; protected static void Setup(TestContext context) { Console.WriteLine("BaseTestClass.BaseTestInitialize()"); _config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); // check for CP77_DIR environment variable first // overrides hardcoded appsettings.json var CP77_DIR = System.Environment.GetEnvironmentVariable("CP77_DIR", EnvironmentVariableTarget.User); if (!string.IsNullOrEmpty(CP77_DIR) && new DirectoryInfo(CP77_DIR).Exists) _gameDirectoryPath = CP77_DIR; else _gameDirectoryPath = _config.GetSection(GameDirectorySetting).Value; if (string.IsNullOrEmpty(_gameDirectoryPath)) throw new ConfigurationErrorsException($"'{GameDirectorySetting}' is not configured"); var gameDirectory = new DirectoryInfo(_gameDirectoryPath); if (!gameDirectory.Exists) throw new ConfigurationErrorsException($"'{GameDirectorySetting}' is not a valid directory"); WriteToFile = Boolean.Parse(_config.GetSection(WriteToFileSetting).Value); ServiceLocator.Default.RegisterType(); ServiceLocator.Default.RegisterType(); var hashService = ServiceLocator.Default.ResolveType(); hashService.ReloadLocally(); DirectoryInfo gameArchiveDir; try { gameArchiveDir = new DirectoryInfo(Path.Combine(gameDirectory.FullName, "archive", "pc", "content")); } catch (Exception e) { Console.WriteLine(e); throw; } bm = new ArchiveManager(gameArchiveDir); GroupedFiles = bm.GroupedFiles; } } } ================================================ FILE: CP77.MSTests/appsettings.json ================================================ { "GameDirectory": "D:\\Program Files\\Steam\\steamapps\\common\\Cyberpunk 2077", "WriteToFile": true } ================================================ FILE: CP77Tools/CP77Tools.csproj ================================================  Exe net5.0-windows 1.1.0.2 1.1.0.2 true false win-x64 true true embedded Always Always Always PreserveNewest ================================================ FILE: CP77Tools/Commands/CR2WCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; namespace CP77Tools.Commands { public class CR2WCommand : Command { private static string Name = "cr2w"; private static string Description = "Target a specific cr2w (extracted) file and dumps file information."; public CR2WCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path to a cr2w file.")); AddOption(new Option(new []{"--outpath", "-o"}, "Output path.")); AddOption(new Option(new[] {"--all", "-a"}, "Dump all information.")); AddOption(new Option(new[] {"--chunks", "-c"}, "Dump all class information of file.")); Handler = CommandHandler.Create(Tasks.ConsoleFunctions.Cr2wTask); } } } ================================================ FILE: CP77Tools/Commands/DumpCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using CP77Tools.Tasks; namespace CP77Tools.Commands { public class DumpCommand : Command { private static string Name = "dump"; private static string Description = "Target an archive or a directory to dump archive information."; public DumpCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path to .archive or to a directory (runs over all archives in directory).")); AddOption(new Option(new[] {"--imports", "-i"}, "Dump all imports (all filenames that are referenced by all files in the archive).")); AddOption(new Option(new[] {"--missinghashes", "-m"}, "List all missing hashes of all input archives.")); AddOption(new Option(new[] {"--texinfo"}, "Dump all xbm info.")); AddOption(new Option(new[] {"--classinfo"}, "Dump all class info.")); AddOption(new Option(new[] {"--dump", "-d"}, "Dump archive information.")); AddOption(new Option(new[] {"--list", "-l"}, "List contents of archive.")); Handler = CommandHandler.Create(ConsoleFunctions.DumpTask); } } } ================================================ FILE: CP77Tools/Commands/ExportCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using WolvenKit.Common.DDS; namespace CP77Tools.Commands { public class ExportCommand : Command { private static string Name = "export"; private static string Description = "Export a file or a list of files into raw files."; public ExportCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path. Must be a file or a list of files.")); AddOption(new Option(new[] { "--uext" }, "Uncook extension (tga, bmp, jpg, png, dds). Default is tga.")); Handler = CommandHandler.Create(Tasks.ConsoleFunctions.ExportTask); } } } ================================================ FILE: CP77Tools/Commands/HashCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using CP77Tools.Tasks; namespace CP77Tools.Commands { public class HashCommand : Command { private static string Name = "hash"; private static string Description = "Some helper functions related to hashes."; public HashCommand() : base(Name, Description) { AddOption(new Option(new[] {"--input", "-i"}, "Create FNV1A hash of given string")); AddOption(new Option(new[] {"--missing", "-m"}, "")); Handler = CommandHandler.Create(ConsoleFunctions.HashTask); } } } ================================================ FILE: CP77Tools/Commands/OodleCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; namespace CP77Tools.Commands { public class OodleCommand : Command { private static string Name = "oodle"; private static string Description = "Some helper functions related to oodle compression."; public OodleCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "")); AddOption(new Option(new []{"--outpath", "-o"}, "")); AddOption(new Option(new[] {"--decompress", "-d"}, "")); Handler = CommandHandler.Create(Tasks.ConsoleFunctions.OodleTask); } } } ================================================ FILE: CP77Tools/Commands/PackCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; namespace CP77Tools.Commands { public class PackCommand : Command { private static string Name = "pack"; private static string Description = "Pack a folder of files into an .archive file."; public PackCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path. Must be a folder or a list of folders.")); AddOption(new Option(new[] {"--outpath", "-o"}, "Output directory to create archive.\nIf not specified, will output twill be in place.")); Handler = CommandHandler.Create(Tasks.ConsoleFunctions.PackTask); } } } ================================================ FILE: CP77Tools/Commands/RebuildCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; namespace CP77Tools.Commands { public class RebuildCommand : Command { private static string Name = "rebuild"; private static string Description = "Recombine split buffers and textures in a folder."; public RebuildCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path. Must be a folder or a list of folders.")); AddOption(new Option(new[] {"--buffers", "-b"}, "Recombine buffers")); AddOption(new Option(new[] {"--textures", "-t"}, "Recombine textures")); AddOption(new Option(new[] {"--import", "-i"}, "Optionally import missing raw files into their redengine formats.")); AddOption(new Option(new[] {"--keep"}, "Optionally keep existing cr2w files intact and only append the buffer")); AddOption(new Option(new[] {"--clean"}, "Optionally remove buffers and textures after successful recombination.")); AddOption(new Option(new[] {"--unsaferaw"}, "Optionally add raw assets (dds textures, fbx) as buffers without check.")); Handler = CommandHandler.Create(Tasks.ConsoleFunctions.RebuildTask); } } } ================================================ FILE: CP77Tools/Commands/UnbundleCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using CP77Tools.Tasks; namespace CP77Tools.Commands { public class UnbundleCommand : Command { private static string Name = "unbundle"; private static string Description = "Target an archive to extract files."; public UnbundleCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path to .archive.")); AddOption(new Option(new[] {"--outpath", "-o"}, "Output directory to extract files to.")); AddOption(new Option(new[] {"--pattern", "-w"}, "Use optional search pattern, e.g. *.ink. If both regex and pattern is defined, pattern will be used first")); AddOption(new Option(new[] {"--regex", "-r"}, "Use optional regex pattern.")); AddOption(new Option(new[] {"--hash"}, "Extract single file with given hash. If a path is supplied it extracts all hashes from that.")); Handler = CommandHandler.Create(ConsoleFunctions.UnbundleTask); } } } ================================================ FILE: CP77Tools/Commands/UncookCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using CP77Tools.Tasks; using WolvenKit.Common.DDS; namespace CP77Tools.Commands { public class UncookCommand : Command { private static string Name = "uncook"; private static string Description = "Target an archive to uncook files."; public UncookCommand() : base(Name, Description) { AddOption(new Option(new[] {"--path", "-p"}, "Input path to .archive.")); AddOption(new Option(new[] {"--outpath", "-o"}, "Output directory to extract files to.")); AddOption(new Option(new[] {"--pattern", "-w"}, "Use optional search pattern, e.g. *.ink. If both regex and pattern is defined, pattern will be used first")); AddOption(new Option(new[] {"--regex", "-r"}, "Use optional regex pattern.")); AddOption(new Option(new[] {"--uext"}, "Uncook extension (tga, bmp, jpg, png, dds). Default is tga.")); AddOption(new Option(new[] { "--flip", "-f" }, "Flips textures vertically")); AddOption(new Option(new[] {"--hash"}, "Extract single file with given hash.")); Handler = CommandHandler.Create(ConsoleFunctions.UncookTask); } } } ================================================ FILE: CP77Tools/Extensions/CommandLineExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace CP77Tools.Extensions { public class CommandLineExtensions { public static IEnumerable ParseText(String line, Char delimiter, Char textQualifier) { if (line == null) yield break; else { Char prevChar = '\0'; Char nextChar = '\0'; Char currentChar = '\0'; Boolean inString = false; StringBuilder token = new StringBuilder(); for (int i = 0; i < line.Length; i++) { currentChar = line[i]; if (i > 0) prevChar = line[i - 1]; else prevChar = '\0'; if (i + 1 < line.Length) nextChar = line[i + 1]; else nextChar = '\0'; if (currentChar == textQualifier && prevChar != 0x5c && !inString) { inString = true; continue; } if (currentChar == textQualifier && inString) { inString = false; continue; } if (currentChar == delimiter && !inString) { yield return token.ToString(); token = token.Remove(0, token.Length); continue; } token = token.Append(currentChar); } yield return token.ToString(); } } } } ================================================ FILE: CP77Tools/Program.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Catel.IoC; using System.CommandLine; using System.ComponentModel; using System.Configuration; using System.IO; using System.Reflection; using CP77Tools.Commands; using CP77Tools.Extensions; using Luna.ConsoleProgressBar; using Microsoft.Win32; using WolvenKit.Common.Oodle; using WolvenKit.Common.Services; namespace CP77Tools { class Program { [STAThread] public static async Task Main(string[] args) { ServiceLocator.Default.RegisterType(); ServiceLocator.Default.RegisterType(); var logger = ServiceLocator.Default.ResolveType(); var hashService = ServiceLocator.Default.ResolveType(); logger.OnStringLogged += delegate (object? sender, LogStringEventArgs args) { switch (args.Logtype) { case Logtype.Error: Console.ForegroundColor = ConsoleColor.Red; break; case Logtype.Important: Console.ForegroundColor = ConsoleColor.Yellow; break; case Logtype.Success: Console.ForegroundColor = ConsoleColor.Green; break; case Logtype.Normal: case Logtype.Wcc: break; default: throw new ArgumentOutOfRangeException(); } Console.WriteLine("[" + args.Logtype + "]" + args.Message); Console.ResetColor(); }; var rootCommand = new RootCommand { new UnbundleCommand(), new UncookCommand(), new RebuildCommand(), new PackCommand(), new ExportCommand(), new DumpCommand(), new CR2WCommand(), new HashCommand(), new OodleCommand(), }; //await ConsoleFunctions.UpdateHashesAsync(); hashService.ReloadLocally(); // try get oodle dll from game if (!TryCopyOodleLib()) { logger.LogString("Could not automatically find oo2ext_7_win64.dll. " + "Please manually copy and paste the dll found here Cyberpunk 2077\\bin\\x64\\oo2ext_7_win64.dll into this folder: " + $"{AppDomain.CurrentDomain.BaseDirectory}."); } // Run if (args == null || args.Length == 0) { // write welcome message rootCommand.InvokeAsync("-h").Wait(); while (true) { string line = System.Console.ReadLine(); if (line == "q()") return; var pb = new ConsoleProgressBar() { DisplayBars = false, DisplayPercentComplete = false, DisplayAnimation = false }; var parsed = CommandLineExtensions.ParseText(line, ' ', '"'); logger.PropertyChanged += OnLoggerOnPropertyChanged; void OnLoggerOnPropertyChanged(object? sender, PropertyChangedEventArgs args) { switch (args.PropertyName) { case "Progress": { if (logger.Progress.Item1 == 0) { pb = new ConsoleProgressBar() { DisplayBars = true, DisplayAnimation = false }; } pb?.Report(logger.Progress.Item1); if (logger.Progress.Item1 == 1) { System.Threading.Thread.Sleep(1000); Console.WriteLine(); pb?.Dispose(); pb = null; } break; } default: break; } } rootCommand.InvokeAsync(parsed.ToArray()).Wait(); await WriteLog(); logger.PropertyChanged -= OnLoggerOnPropertyChanged; } } else { rootCommand.InvokeAsync(args).Wait(); await WriteLog(); } async Task WriteLog() { if (string.IsNullOrEmpty(logger.ErrorLogStr)) return; var t = DateTime.Now.ToString("yyyyMMddHHmmss"); var fi = new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), $"errorlogs/log_{t}.txt")); if (fi.Directory != null) { Directory.CreateDirectory(fi.Directory.FullName); var log = logger.ErrorLogStr; await File.WriteAllTextAsync(fi.FullName, log); } else { } } } private delegate void StrDelegate(string value); private static string TryGetGameInstallDir() { var cp77BinDir = ""; var cp77exe = ""; // check for CP77_DIR environment variable first var CP77_DIR = System.Environment.GetEnvironmentVariable("CP77_DIR", EnvironmentVariableTarget.User); if (!string.IsNullOrEmpty(CP77_DIR) && new DirectoryInfo(CP77_DIR).Exists) cp77BinDir = Path.Combine(CP77_DIR, "bin", "x64"); if (File.Exists(Path.Combine(cp77BinDir, "Cyberpunk2077.exe"))) return cp77BinDir; // else: look for install location const string uninstallkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; const string uninstallkey2 = "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; const string gameName = "Cyberpunk 2077"; const string exeName = "Cyberpunk2077.exe"; var exePath = ""; StrDelegate strDelegate = msg => cp77exe = msg; try { Parallel.ForEach(Registry.LocalMachine.OpenSubKey(uninstallkey)?.GetSubKeyNames(), item => { var programName = Registry.LocalMachine.OpenSubKey(uninstallkey + item) ?.GetValue("DisplayName"); var installLocation = Registry.LocalMachine.OpenSubKey(uninstallkey + item) ?.GetValue("InstallLocation"); if (programName != null && installLocation != null) { if (programName.ToString().Contains(gameName) || programName.ToString().Contains(gameName)) { exePath = Directory.GetFiles(installLocation.ToString(), exeName, SearchOption.AllDirectories).First(); } } strDelegate.Invoke(exePath); }); Parallel.ForEach(Registry.LocalMachine.OpenSubKey(uninstallkey2)?.GetSubKeyNames(), item => { var programName = Registry.LocalMachine.OpenSubKey(uninstallkey2 + item) ?.GetValue("DisplayName"); var installLocation = Registry.LocalMachine.OpenSubKey(uninstallkey2 + item) ?.GetValue("InstallLocation"); if (programName != null && installLocation != null) { if (programName.ToString().Contains(gameName) || programName.ToString().Contains(gameName)) { if (Directory.Exists(installLocation.ToString())) exePath = Directory.GetFiles(installLocation.ToString(), exeName, SearchOption.AllDirectories).First(); } } strDelegate.Invoke(exePath); }); if (File.Exists(cp77exe)) cp77BinDir = new FileInfo(cp77exe).Directory.FullName; } catch (Exception e) { } if (string.IsNullOrEmpty(cp77BinDir)) return null; if (!File.Exists(Path.Combine(cp77BinDir, "Cyberpunk2077.exe"))) return null; return cp77BinDir; } private static bool TryCopyOodleLib() { var ass = AppDomain.CurrentDomain.BaseDirectory; var destFileName = Path.Combine(ass, "oo2ext_7_win64.dll"); if (File.Exists(destFileName)) return true; var cp77BinDir = TryGetGameInstallDir(); if (string.IsNullOrEmpty(cp77BinDir)) return false; // copy oodle dll var oodleInfo = new FileInfo(Path.Combine(cp77BinDir, "oo2ext_7_win64.dll")); if (!oodleInfo.Exists) return false; if (!File.Exists(destFileName)) oodleInfo.CopyTo(destFileName); return true; } } } ================================================ FILE: CP77Tools/Tasks/Cr2wTask.cs ================================================ using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; using CP77.CR2W; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { public static void Cr2wTask(string[] path, string outpath, bool all, bool chunks) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { Cr2wTaskInner(file, outpath, all, chunks); }); } private static int Cr2wTaskInner(string path, string outpath, bool all, bool chunks) { // initial checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path.", Logtype.Error); return 0; } var inputFileInfo = new FileInfo(path); if (!inputFileInfo.Exists) { logger.LogString("Input file does not exist.", Logtype.Error); return 0; } var outputDirInfo = string.IsNullOrEmpty(outpath) ? inputFileInfo.Directory : new DirectoryInfo(outpath); if (outputDirInfo == null || !outputDirInfo.Exists) { logger.LogString("Output directory is not valid.", Logtype.Error); return 0; } var f = File.ReadAllBytes(inputFileInfo.FullName); using var ms = new MemoryStream(f); using var br = new BinaryReader(ms); var cr2w = new CR2WFile(); if (all) { cr2w.ReadImportsAndBuffers(br); var obj = new Cr2wChunkInfo { Filename = inputFileInfo.FullName }; obj.Stringdict = cr2w.StringDictionary; obj.Imports = cr2w.Imports; obj.Buffers = cr2w.Buffers; obj.Chunks = cr2w.Chunks; foreach (var chunk in cr2w.Chunks) { obj.ChunkData.Add(chunk.GetDumpObject(br)); } //write File.WriteAllText(Path.Combine(outputDirInfo.FullName, $"{inputFileInfo.Name}.info.json"), JsonConvert.SerializeObject(obj, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.Auto })); logger.LogString($"Finished. Dump file written to {Path.Combine(outputDirInfo.FullName, $"{inputFileInfo.Name}.info.json")}", Logtype.Success); } if (chunks) { br.BaseStream.Seek(0, SeekOrigin.Begin); cr2w.Read(br); //write File.WriteAllText(Path.Combine(outputDirInfo.FullName, $"{inputFileInfo.Name}.json"), JsonConvert.SerializeObject(cr2w, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.None })); logger.LogString($"Finished. Dump file written to {Path.Combine(outputDirInfo.FullName, $"{inputFileInfo.Name}.json")}", Logtype.Success); } return 1; } } } ================================================ FILE: CP77Tools/Tasks/DumpTask.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; //using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Catel.IoC; using CP77.CR2W.Archive; using Newtonsoft.Json; using WolvenKit.Common; using WolvenKit.Common.Extensions; using CP77.CR2W; using CP77.CR2W.Reflection; using CP77.CR2W.Types; using WolvenKit.Common.FNV1A; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public class ArchiveDumpObject { public ConcurrentDictionary FileDictionary { get; set; } public ConcurrentDictionary TextureDictionary { get; set; } public string Filename { get; set; } } public class Cr2wChunkInfo { public Dictionary Stringdict { get; set; } public List Imports { get; set; } public List Buffers { get; set; } public List Chunks { get; set; } public List ChunkData { get; } = new List(); public string Filename { get; set; } } public class Cr2wTextureInfo { public string Filename { get; set; } public ushort width { get; set; } public ushort height { get; set; } public byte mips { get; set; } public ushort slicecount { get; set; } public uint alignment { get; set; } public CEnum compression { get; set; } public CEnum Group { get; set; } public CEnum rawFormat { get; set; } } public static partial class ConsoleFunctions { private static byte[] MAGIC = {0x43, 0x52, 0x32, 0x57}; public static void DumpTask(string[] path, bool imports, bool missinghashes, bool texinfo, bool classinfo, bool dump, bool list) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { DumpTaskInner(file, imports, missinghashes, texinfo, classinfo, dump, list); }); } public static int DumpTaskInner(string path, bool imports, bool missinghashes, bool texinfo, bool classinfo, bool dump, bool list) { #region checks if (string.IsNullOrEmpty(path)) { ConsoleFunctions.logger.LogString("Please fill in an input path",Logtype.Error); return 0; } var isDirectory = false; var inputFileInfo = new FileInfo(path); var inputDirInfo = new DirectoryInfo(path); if (!inputFileInfo.Exists) { if (!inputDirInfo.Exists) return 0; else isDirectory = true; } #endregion var archives = new List(); if (isDirectory) { archives.AddRange(inputDirInfo .GetFiles("*.archive", SearchOption.AllDirectories) .Select(_ => new Archive(_.FullName))); } else { archives.Add(new Archive(inputFileInfo.FullName)); } var mainController = ServiceLocator.Default.ResolveType(); var logger = ServiceLocator.Default.ResolveType(); var typedict = new ConcurrentDictionary>(); // Parallel foreach (var ar in archives) { if (classinfo) { // using var mmf = MemoryMappedFile.CreateFromFile(ar.Filepath, FileMode.Open, // ar.Filepath.GetHashMD5(), 0, // MemoryMappedFileAccess.Read); var fileinfo = ar.Files.Values; var query = fileinfo.GroupBy( ext => Path.GetExtension(ext.FileName), file => file, (ext, finfo) => new { Key = ext, File = fileinfo.Where(_ => Path.GetExtension(_.FileName) == ext) }).ToList(); var total = query.Count; logger.LogString($"Exporting {total} bundle entries "); Thread.Sleep(1000); int progress = 0; logger.LogProgress(0); // foreach extension Parallel.ForEach(query, result => { if (!string.IsNullOrEmpty(result.Key)) { Parallel.ForEach(result.File, fi => { var (f, b) = ar.GetFileData(fi.NameHash64, false); using var ms = new MemoryStream(f); using var br = new BinaryReader(ms); var cr2w = new CR2WFile(); try { cr2w.ReadImportsAndBuffers(br); } catch (Exception e) { return; } foreach (var chunk in cr2w.Chunks) { var o = chunk.GetDumpObject(br); if (o != null) Register(o); } }); } Interlocked.Increment(ref progress); logger.LogProgress(progress / (float)total); logger.LogString($"Dumped extension {result.Key}", Logtype.Normal); }); } if (imports || texinfo) { // using var mmf = MemoryMappedFile.CreateFromFile(ar.Filepath, FileMode.Open, // ar.Filepath.GetHashMD5(), 0, // MemoryMappedFileAccess.Read); var fileDictionary = new ConcurrentDictionary(); var texDictionary = new ConcurrentDictionary(); // get info var count = ar.FileCount; logger.LogString($"Exporting {count} bundle entries "); Thread.Sleep(1000); int progress = 0; logger.LogProgress(0); Parallel.For(0, count, i => { var entry = ar.Files.ToList()[i]; var hash = entry.Key; var filename = string.IsNullOrEmpty(entry.Value.FileName) ? hash.ToString() : entry.Value.FileName; if (imports) { var (f, buffers) = ar.GetFileData(hash, false); // check if cr2w file if (f.Length < 4) return; var id = f.Take(4); if (!id.SequenceEqual(MAGIC)) return; var cr2w = new CR2WFile(); using var ms = new MemoryStream(f); using var br = new BinaryReader(ms); cr2w.ReadImportsAndBuffers(br); var obj = new Cr2wChunkInfo { Filename = filename, Imports = cr2w.Imports }; fileDictionary.AddOrUpdate(hash, obj, (arg1, o) => obj); } if (texinfo) { if (!string.IsNullOrEmpty(entry.Value.FileName) && entry.Value.FileName.Contains(".xbm")) { var (f, buffers) = ar.GetFileData(hash, false); // check if cr2w file if (f.Length < 4) return; var id = f.Take(4); if (!id.SequenceEqual(MAGIC)) return; var cr2w = new CR2WFile(); using var ms = new MemoryStream(f); using var br = new BinaryReader(ms); var result = cr2w.Read(br); if (result != EFileReadErrorCodes.NoError) return; if (!(cr2w.Chunks.FirstOrDefault()?.data is CBitmapTexture xbm) || !(cr2w.Chunks[1]?.data is rendRenderTextureBlobPC blob)) return; // create dds header var texinfo = new Cr2wTextureInfo() { Filename = filename, width = blob.Header.SizeInfo.Width.val, height = blob.Header.SizeInfo.Height.val, mips = blob.Header.TextureInfo.MipCount.val, slicecount = blob.Header.TextureInfo.SliceCount.val, alignment = blob.Header.TextureInfo.DataAlignment.val, compression = xbm.Setup.Compression, Group = xbm.Setup.Group, rawFormat = xbm.Setup.RawFormat, }; texDictionary.AddOrUpdate(hash, texinfo, (arg1, o) => texinfo); } } Interlocked.Increment(ref progress); logger.LogProgress(progress / (float)count); }); // write var arobj = new ArchiveDumpObject() { Filename = ar.ArchiveAbsolutePath, FileDictionary = fileDictionary, TextureDictionary = texDictionary }; if (imports) { using var hwriter = File.CreateText($"{ar.ArchiveAbsolutePath}.hashes.csv"); hwriter.WriteLine("String,Hash"); List allimports = new List(); foreach (var (key, value) in arobj.FileDictionary) { if (value.Imports == null) continue; allimports.AddRange(value.Imports.Select(import => import.DepotPathStr)); } foreach (var str in allimports.Distinct()) { var hash = FNV1A64HashAlgorithm.HashString(str); if (!mainController.Hashdict.ContainsKey(hash)) hwriter.WriteLine($"{str},{hash}"); } logger.LogString($"Finished. Dump file written to {ar.ArchiveAbsolutePath}.", Logtype.Success); //write File.WriteAllText($"{ar.ArchiveAbsolutePath}.json", JsonConvert.SerializeObject(arobj, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.None })); logger.LogString($"Finished. Dump file written to {inputFileInfo.FullName}.json", Logtype.Success); } if (texinfo) { //write File.WriteAllText($"{ar.ArchiveAbsolutePath}.textures.json", JsonConvert.SerializeObject(arobj, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.None })); logger.LogString($"Finished. Dump file written to {inputFileInfo.FullName}.json", Logtype.Success); } } // TODO: add this here if (dump) { File.WriteAllText($"{ar.ArchiveAbsolutePath}.json", JsonConvert.SerializeObject(ar, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.None })); logger.LogString($"Finished dumping {ar.ArchiveAbsolutePath}.", Logtype.Success); } if (list) { foreach (var entry in ar.Files) { logger.LogString(entry.Value.FileName, Logtype.Normal); } } } if (classinfo) { //write class definitions var outdir = isDirectory ? Path.Combine(inputDirInfo.FullName, "ClassDefinitions") : Path.Combine(inputFileInfo.Directory.FullName, "ClassDefinitions"); Directory.CreateDirectory(outdir); var outfile = Path.Combine(outdir, "classdefinitions.txt"); var outfileS = Path.Combine(outdir, "classdefinitions_simple.json"); var text = ""; foreach (var (typename, variables) in typedict) { //write var sb = new StringBuilder($"[REDMeta] public class {typename} : CVariable {{\r\n"); var variableslist = variables.ToList(); for (int i = 0; i < variableslist.Count; i++) { var typ = variableslist[i].Split(' ').First(); var nam = variableslist[i].Split(' ').Last(); var wktype = REDReflection.GetWKitBaseTypeFromREDBaseType(typ); if (string.IsNullOrEmpty(nam)) nam = "Missing"; if (string.IsNullOrEmpty(typ)) typ = "Missing"; sb.Append($"\t[Ordinal({i})] [RED(\"{nam}\")] public {wktype} {nam.FirstCharToUpper()} {{ get; set; }}\r\n"); } sb.Append( $"public {typename}(CR2WFile cr2w, CVariable parent, string name) : base(cr2w, parent, name) {{ }}\r\n"); sb.Append("}\r\n"); text += sb.ToString(); } File.WriteAllText(outfile, text); //write File.WriteAllText(outfileS, JsonConvert.SerializeObject(typedict, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, TypeNameHandling = TypeNameHandling.None })); logger.LogString("Done.", Logtype.Success); } if (missinghashes) { var missinghashtxt = isDirectory ? Path.Combine(inputDirInfo.FullName, "missinghashes.txt") : $"{inputFileInfo.FullName}.missinghashes.txt"; using var mwriter = File.CreateText(missinghashtxt); foreach (var ar in archives) { var ctr = 0; foreach (var (hash, fileInfoEntry) in ar.Files) { if (fileInfoEntry.NameOrHash == hash.ToString()) { mwriter.WriteLine(hash); ctr++; } } logger.LogString($"{ar.ArchiveAbsolutePath} - missing: {ctr}", Logtype.Normal); } } return 1; void Register(CR2WExportWrapper.Cr2wVariableDumpObject o) { if (o?.Type == null) return; o.Variables ??= new List(); IEnumerable vars = o.Variables.Select(_ => _.ToSimpleString()); if (typedict.ContainsKey(o.Type)) { var existing = typedict[o.Type]; var newlist = o.Variables.Select(_ => _.ToSimpleString()); if (existing != null) vars = existing.Union(newlist); } typedict.AddOrUpdate(o.Type, vars, (arg1, ol) => ol); foreach (var oVariable in o.Variables) { // generic types (arrays, handles, refs) if (oVariable.Type != null && oVariable.Type.Contains(":")) { var gentyp = oVariable.Type.Split(":").First(); var innertype = oVariable.Type.Substring(gentyp.Length + 1); var innertype2 = oVariable.Type[(gentyp.Length + 1)]; if (gentyp == "array") { oVariable.Type = innertype; Register(oVariable); } else { continue; } } Register(oVariable); } } } } } ================================================ FILE: CP77Tools/Tasks/ExportTask.cs ================================================ using System.IO; using System.Threading.Tasks; using CP77.CR2W; using WolvenKit.Common.DDS; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { public static void ExportTask(string[] path, EUncookExtension uncookext) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { ExportTaskInner(file, uncookext); }); } private static int ExportTaskInner(string path, EUncookExtension uncookext) { #region checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path.", Logtype.Error); return 0; } var inputFileInfo = new FileInfo(path); if (!inputFileInfo.Exists) { logger.LogString("Input file does not exist.", Logtype.Error); return 0; } #endregion if (ModTools.Export(new FileInfo(path), uncookext) == 1) { logger.LogString($"Successfully exported {path}.", Logtype.Success); } else { logger.LogString($"Failed to export {path}.", Logtype.Error); } return 1; } } } ================================================ FILE: CP77Tools/Tasks/HashTask.cs ================================================ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Catel.IoC; using WolvenKit.Common.FNV1A; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { public static int HashTask(string[] input, bool missing) { #region checks foreach (var s in input) { if (!string.IsNullOrEmpty(s)) logger.LogString(FNV1A64HashAlgorithm.HashString(s).ToString(), Logtype.Normal); } #endregion if (missing) { var missingh = File.ReadAllLines(@"C:\Gog\Cyberpunk 2077\archive\pc\content\missinghashes.txt"); var lines = File.ReadAllLines(@"X:\cp77\langs-work.txt"); var Hashdict = new Dictionary(); var bad = new Dictionary(); foreach (var line in lines) { var hash = FNV1A64HashAlgorithm.HashString(line); if (missingh.Contains(hash.ToString())) { if (!Hashdict.ContainsKey(hash)) Hashdict.Add(hash, line); } else { if (!bad.ContainsKey(hash)) bad.Add(hash, line); } } } return 1; } } } ================================================ FILE: CP77Tools/Tasks/OodleTask.cs ================================================ using System; using System.IO; using System.Linq; using WolvenKit.Common.Oodle; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { public static int OodleTask(string path, string outpath, bool decompress) { if (string.IsNullOrEmpty(path)) return 0; if (string.IsNullOrEmpty(outpath)) { outpath = path; } if (decompress) { var file = File.ReadAllBytes(path); using var ms = new MemoryStream(file); using var br = new BinaryReader(ms); var oodleCompression = br.ReadBytes(4); if (!(oodleCompression.SequenceEqual(new byte[] { 0x4b, 0x41, 0x52, 0x4b }))) throw new NotImplementedException(); var size = br.ReadUInt32(); var buffer = br.ReadBytes(file.Length - 8); byte[] unpacked = new byte[size]; long unpackedSize = OodleHelper.Decompress(buffer, unpacked); using var msout = new MemoryStream(); using var bw = new BinaryWriter(msout); bw.Write(unpacked); File.WriteAllBytes($"{outpath}.kark", msout.ToArray()); } return 1; } } } ================================================ FILE: CP77Tools/Tasks/PackTask.cs ================================================ using System.IO; using System.Threading.Tasks; using CP77.CR2W; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { /// /// Packs a folder or list of folders to .archive files. /// /// /// public static void PackTask(string[] path, string outpath) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { PackTaskInner(file, outpath); }); } private static void PackTaskInner(string path, string outpath, int cp = 0) { #region checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path", Logtype.Error); return; } var inputDirInfo = new DirectoryInfo(path); if (!Directory.Exists(path) || !inputDirInfo.Exists) { logger.LogString("Input path does not exist", Logtype.Error); return; } var basedir = inputDirInfo; if (basedir?.Parent == null) return; DirectoryInfo outDir; if (string.IsNullOrEmpty(outpath)) { outDir = basedir.Parent; } else { outDir = new DirectoryInfo(outpath); if (!outDir.Exists) outDir = Directory.CreateDirectory(outpath); } #endregion var ar = ModTools.Pack(basedir, outDir); if (ar != null) logger.LogString($"Finished packing {ar.ArchiveAbsolutePath}.", Logtype.Success); else logger.LogString($"Packing failed.", Logtype.Error); return; } } } ================================================ FILE: CP77Tools/Tasks/RebuildTask.cs ================================================ using System.IO; using System.Threading.Tasks; using CP77.CR2W; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { /// /// Recombine split buffers and textures in a folder. /// /// /// public static void RebuildTask(string[] path, bool buffers, bool textures, bool import, bool keep, bool clean, bool unsaferaw ) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, p => { RebuildTaskInner(p, buffers, textures, import, keep, clean, unsaferaw); }); } private static void RebuildTaskInner(string path, bool buffers, bool textures, bool import, bool keep, bool clean, bool unsaferaw ) { #region checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path", Logtype.Error); return; } var inputDirInfo = new DirectoryInfo(path); if (!Directory.Exists(path) || !inputDirInfo.Exists) { logger.LogString("Input path does not exist", Logtype.Error); return; } var basedir = inputDirInfo; if (basedir?.Parent == null) return; #endregion ModTools.Recombine(basedir, buffers, textures, import, keep, clean, unsaferaw); return; } } } ================================================ FILE: CP77Tools/Tasks/UnbundleTask.cs ================================================ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using CP77.CR2W.Archive; using Catel.IoC; using CP77.CR2W; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { private static readonly ILoggerService logger = ServiceLocator.Default.ResolveType(); public static void UnbundleTask(string[] path, string outpath, string hash, string pattern, string regex) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { UnbundleTaskInner(file, outpath, hash, pattern, regex); }); } private static void UnbundleTaskInner(string path, string outpath, string hash, string pattern, string regex) { #region checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path", Logtype.Error); return; } var inputFileInfo = new FileInfo(path); var inputDirInfo = new DirectoryInfo(path); if (!inputFileInfo.Exists && !inputDirInfo.Exists) { logger.LogString("Input path does not exist", Logtype.Error); return; } if (inputFileInfo.Exists && inputFileInfo.Extension != ".archive") { logger.LogString("Input file is not an .archive", Logtype.Error); return; } else if (inputDirInfo.Exists && inputDirInfo.GetFiles().All(_ => _.Extension != ".archive")) { logger.LogString("No .archive file to process in the input directory", Logtype.Error); return; } var isDirectory = !inputFileInfo.Exists; var basedir = inputFileInfo.Exists ? new FileInfo(path).Directory : inputDirInfo; #endregion List archiveFileInfos; if (isDirectory) { var archiveManager = new ArchiveManager(basedir); // TODO: use the manager here? archiveFileInfos = archiveManager.Archives.Select(_ => new FileInfo(_.Value.ArchiveAbsolutePath)).ToList(); } else { archiveFileInfos = new List {inputFileInfo}; } foreach (var processedarchive in archiveFileInfos) { // get outdirectory DirectoryInfo outDir; if (string.IsNullOrEmpty(outpath)) { outDir = Directory.CreateDirectory(Path.Combine( basedir.FullName, processedarchive.Name.Replace(".archive", ""))); } else { outDir = new DirectoryInfo(outpath); if (!outDir.Exists) { outDir = Directory.CreateDirectory(outpath); } if (inputDirInfo.Exists) { outDir = Directory.CreateDirectory(Path.Combine( outDir.FullName, processedarchive.Name.Replace(".archive", ""))); } } // read archive var ar = new Archive(processedarchive.FullName); var isHash = ulong.TryParse(hash, out ulong hashNumber); // run { if (!isHash && File.Exists(hash)) { var hashlist = File.ReadAllLines(hash) .ToList().Select(_ => ulong.TryParse(_, out ulong res) ? res : 0); logger.LogString($"Extracing all files from the hashlist ({hashlist.Count()}hashes) ...", Logtype.Normal); foreach (var hash_num in hashlist) { ar.ExtractSingle(hash_num, outDir); logger.LogString($" {ar.ArchiveAbsolutePath}: Extracted one file: {hash_num}", Logtype.Success); } logger.LogString($"Bulk extraction from hashlist file completed!", Logtype.Success); } else if (isHash && hashNumber != 0) { ar.ExtractSingle(hashNumber, outDir); logger.LogString($" {ar.ArchiveAbsolutePath}: Extracted one file: {hashNumber}", Logtype.Success); } else { var r = ar.ExtractAll(outDir, pattern, regex); logger.LogString($"{ar.ArchiveAbsolutePath}: Extracted {r.Item1.Count}/{r.Item2} files.", Logtype.Success); } } } return; } } } ================================================ FILE: CP77Tools/Tasks/UncookTask.cs ================================================ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using CP77.CR2W.Archive; using CP77.CR2W; using WolvenKit.Common.DDS; using WolvenKit.Common.Services; namespace CP77Tools.Tasks { public static partial class ConsoleFunctions { public static void UncookTask(string[] path, string outpath, EUncookExtension uext, bool flip, ulong hash, string pattern, string regex) { if (path == null || path.Length < 1) { logger.LogString("Please fill in an input path", Logtype.Error); return; } Parallel.ForEach(path, file => { UncookTaskInner(file, outpath, uext, flip, hash, pattern, regex); }); } private static void UncookTaskInner(string path, string outpath, EUncookExtension uext, bool flip, ulong hash, string pattern, string regex) { #region checks if (string.IsNullOrEmpty(path)) { logger.LogString("Please fill in an input path", Logtype.Error); return; } var inputFileInfo = new FileInfo(path); var inputDirInfo = new DirectoryInfo(path); if (!inputFileInfo.Exists && !inputDirInfo.Exists) { logger.LogString("Input path does not exist", Logtype.Error); return; } if (inputFileInfo.Exists && inputFileInfo.Extension != ".archive") { logger.LogString("Input file is not an .archive", Logtype.Error); return; } else if (inputDirInfo.Exists && inputDirInfo.GetFiles().All(_ => _.Extension != ".archive")) { logger.LogString("No .archive file to process in the input directory", Logtype.Error); return; } var isDirectory = !inputFileInfo.Exists; var basedir = inputFileInfo.Exists ? new FileInfo(path).Directory : inputDirInfo; #endregion List archiveFileInfos; if (isDirectory) { var archiveManager = new ArchiveManager(basedir); // TODO: use the manager here? archiveFileInfos = archiveManager.Archives.Select(_ => new FileInfo(_.Value.ArchiveAbsolutePath)).ToList(); } else { archiveFileInfos = new List {inputFileInfo}; } foreach (var processedarchive in archiveFileInfos) { // get outdirectory DirectoryInfo outDir; if (string.IsNullOrEmpty(outpath)) { outDir = Directory.CreateDirectory(Path.Combine( basedir.FullName, processedarchive.Name.Replace(".archive", ""))); } else { outDir = new DirectoryInfo(outpath); if (!outDir.Exists) { outDir = Directory.CreateDirectory(outpath); } if (inputDirInfo.Exists) { outDir = Directory.CreateDirectory(Path.Combine( outDir.FullName, processedarchive.Name.Replace(".archive", ""))); } } // read archive var ar = new Archive(processedarchive.FullName); // run if (hash != 0) { ar.UncookSingle(hash, outDir, uext, flip); logger.LogString($" {ar.ArchiveAbsolutePath}: Uncooked one file: {hash}", Logtype.Success); } else { var r = ar.UncookAll(outDir, pattern, regex, uext, flip); logger.LogString($" {ar.ArchiveAbsolutePath}: Uncooked {r.Item1.Count}/{r.Item2} files.", Logtype.Success); } } return; } } } ================================================ FILE: CP77Tools.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30709.64 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CP77Tools", "CP77Tools\CP77Tools.csproj", "{37C3288C-A6D7-4C71-8174-0F2F6D47938E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CP77.MSTests", "CP77.MSTests\CP77.MSTests.csproj", "{78302151-8D4B-407B-A2FE-5774C471F8AA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {37C3288C-A6D7-4C71-8174-0F2F6D47938E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {37C3288C-A6D7-4C71-8174-0F2F6D47938E}.Debug|Any CPU.Build.0 = Debug|Any CPU {37C3288C-A6D7-4C71-8174-0F2F6D47938E}.Release|Any CPU.ActiveCfg = Release|Any CPU {37C3288C-A6D7-4C71-8174-0F2F6D47938E}.Release|Any CPU.Build.0 = Release|Any CPU {78302151-8D4B-407B-A2FE-5774C471F8AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {78302151-8D4B-407B-A2FE-5774C471F8AA}.Debug|Any CPU.Build.0 = Debug|Any CPU {78302151-8D4B-407B-A2FE-5774C471F8AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {78302151-8D4B-407B-A2FE-5774C471F8AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D17FF89E-56D7-4FBF-9C1D-90FA8B7813DD} EndGlobalSection EndGlobal ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 rfuzzo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # CP77Tools [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) [![Discord](https://img.shields.io/discord/717692382849663036.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Epkq79kd96) [![CodeFactor](https://www.codefactor.io/repository/github/wolvenkit/cp77tools/badge)](https://www.codefactor.io/repository/github/wolvenkit/cp77tools) ![GitHub all releases](https://img.shields.io/github/downloads/rfuzzo/cp77tools/total) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/rfuzzo/cp77tools)](https://github.com/WolvenKit/CP77Tools/releases) [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/rfuzzo/cp77tools?include_prereleases)](https://github.com/WolvenKit/CP77Tools/releases) ------------- # ❗ This repository is archived. # ❗ It will be maintained here: https://github.com/WolvenKit/Wolvenkit ------------- Modding tools for the CDPR Cyberpunk 2077 video game. ## Latest Stable Release [![GitHub release (latest by date)](https://img.shields.io/github/v/release/rfuzzo/cp77tools)](https://github.com/WolvenKit/CP77Tools/releases) https://github.com/WolvenKit/CP77Tools/releases ## Latest Beta Release [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/rfuzzo/cp77tools?include_prereleases)](https://github.com/WolvenKit/CP77Tools/releases) https://github.com/WolvenKit/CP77Tools/releases ------------- ❗ **requires NET5.0.** (if you don't have it: https://dotnet.microsoft.com/download/dotnet/5.0) ❗ **The cp77 tools require oo2ext_7_win64.dll to work.** Copy and paste the dll found here `Cyberpunk 2077\bin\x64\oo2ext_7_win64.dll` into the cp77Tools folder. If you are building from source, the dll needs to be in the same folder as the build .exe, e.g. C:\cpmod\CP77Tools\CP77Tools\bin\Debug\net5.0\oo2ext_7_win64.dll ------------- ❓ **Check the wiki for guides and how to use this tool:** https://github.com/WolvenKit/CP77Tools/wiki ## Usage: ❗ **Surround paths with quotation marks** (if the path contains spaces: e.g. `archive -e -p "C:\Cyberpunk 2077\modding"`) * displays the general help: list all commands `--help` * displays help for a specific command, e.g. `archive -h` ### Main functions * extract all files from archive `unbundle -p "PATH TO ARCHIVE"` * extract all textures from archive (supports conversion to tga, bmp, jpg, png, dds) `uncook --uext png -p "PATH TO ARCHIVE"` * rebuild textures and buffers (this step is needed when packing textures!, see the wiki for how to use this command) `rebuild -p "PATH TO FOLDER" -b -t --keep --unsaferaw` * pack a folder into an .archive (see the wiki for how to use this command) `pack -p "PATH TO FOLDER"` ### Debug Options * dumps property info from extracted cr2w file `cr2w -c -p ""`