├── .gitmodules ├── .editorconfig ├── PkgEditor ├── pkgtool.ico ├── Images │ ├── spinner.gif │ ├── File_large.png │ ├── File_small.png │ ├── Folder_large.png │ └── Folder_small.png ├── app.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Program.cs ├── PasscodeEntry.cs ├── LogWindow.cs ├── Views │ ├── View.cs │ ├── PFSView.Designer.cs │ ├── ObjectView.Designer.cs │ ├── PFSView.cs │ ├── ObjectView.cs │ ├── CryptoDebug.cs │ ├── CryptoDebug.resx │ ├── ObjectView.resx │ ├── PFSView.resx │ ├── FileView.cs │ ├── PkgView.resx │ └── SFOView.resx ├── LogWindow.Designer.cs ├── ValidationDialog.cs ├── KeyDB.cs ├── PasscodeEntry.Designer.cs ├── ValidationDialog.Designer.cs ├── LogWindow.resx ├── PasscodeEntry.resx └── ValidationDialog.resx ├── idx.bt ├── README.md ├── LibOrbisPkg ├── Kimie │ ├── Count.cs │ └── Counter.cs ├── Util │ ├── Extensions.cs │ ├── OffsetStream.cs │ ├── ReaderBase.cs │ ├── SubStream.cs │ ├── WriterBase.cs │ ├── MersenneTwister.cs │ └── XtsBlockTransform.cs ├── Rif │ ├── LicenseInfo.cs │ └── LicenseDat.cs ├── Properties │ └── AssemblyInfo.cs ├── PFS │ ├── PFSCWriter.cs │ ├── FlatPathTable.cs │ ├── PfsProperties.cs │ ├── XtsDecryptReader.cs │ ├── PFSCReader.cs │ └── FSTree.cs ├── PlayGo │ ├── Manifest.cs │ └── ChunkDat.cs ├── PKG │ ├── PkgProperties.cs │ ├── PkgWriter.cs │ └── PkgReader.cs ├── LibOrbisPkg.csproj └── GP4 │ └── Gp4Validator.cs ├── Rif.bt ├── SFO.bt ├── CollisionResolver.bt ├── PFSC.bt ├── rifa.bt ├── LibOrbisPkg.Core.sln ├── PlaygoChunkDat.bt ├── LibOrbisPkg.sln ├── .gitattributes ├── appveyor.yml └── .gitignore /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = crlf 5 | -------------------------------------------------------------------------------- /PkgEditor/pkgtool.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/pkgtool.ico -------------------------------------------------------------------------------- /PkgEditor/Images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/Images/spinner.gif -------------------------------------------------------------------------------- /PkgEditor/Images/File_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/Images/File_large.png -------------------------------------------------------------------------------- /PkgEditor/Images/File_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/Images/File_small.png -------------------------------------------------------------------------------- /PkgEditor/Images/Folder_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/Images/Folder_large.png -------------------------------------------------------------------------------- /PkgEditor/Images/Folder_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KimieStar/Pkg-Editor-2023/HEAD/PkgEditor/Images/Folder_small.png -------------------------------------------------------------------------------- /PkgEditor/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /idx.bt: -------------------------------------------------------------------------------- 1 | LittleEndian(); 2 | 3 | char magic[4]; 4 | int num; 5 | char i; 6 | char title[23]; 7 | struct { 8 | char name[16]; 9 | int64 offset; 10 | int64 size; 11 | char unk; 12 | char unk2; 13 | char hmac[8]; 14 | char pad[6]; 15 | } rifs[num]; -------------------------------------------------------------------------------- /PkgEditor/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an enhanced version of maxton's / LibOrbisPkg\ 2 | And some features are going to be added\ 3 | :p 4 | 5 | # Fixed Bugs 6 | App won't freeze anymore when extracting. 7 | 8 | 9 | 10 | # New Features 11 | 12 | ## Progress Bar 13 | To check on your progress ;) 14 | ![image](https://user-images.githubusercontent.com/64313946/216185426-a96a37b8-6cc8-4f6d-ace0-5c0063de2f95.png) 15 | 16 | -------------------------------------------------------------------------------- /LibOrbisPkg/Kimie/Count.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LibOrbisPkg.Kimie 7 | { 8 | public static class Count 9 | { 10 | private static int count = new int(); 11 | private static int MaxCount = new int(); 12 | 13 | public static int Count1 { get => count; set => count = value; } 14 | public static int MaxCount1 { get => MaxCount; set => MaxCount = value; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Rif.bt: -------------------------------------------------------------------------------- 1 | BigEndian(); 2 | char signature[4]; 3 | uint16 version; 4 | int16 unk_06; 5 | int64 PsnId; 6 | int64 startTime; 7 | int64 EndTime; 8 | char contentId[48]; 9 | uint16 license_type; 10 | uint16 drm_type; 11 | uint16 content_type; 12 | uint16 SKU_flag; 13 | uint flags; 14 | uint unk_5C; 15 | uint unk_60; 16 | uint unk_64; 17 | uint unk_flag; 18 | uint16 unk_6c; 19 | uint16 unk_6e; 20 | char pad[464]; 21 | byte disc_key[32]; 22 | byte secret_iv[16]; 23 | byte secret[144]; 24 | byte sig[256]; -------------------------------------------------------------------------------- /SFO.bt: -------------------------------------------------------------------------------- 1 | enum param_fmt { 2 | utf8_special = 0x4, 3 | utf8 = 0x204, 4 | integer = 0x404 5 | }; 6 | 7 | char magic[4]; 8 | int version; 9 | int key_table_start; 10 | int data_table_start; 11 | int num_entries; 12 | struct { 13 | uint16 keyOffset; //offset of keytable + keyOffset 14 | param_fmt format; //enum (see below) 15 | uint32 paramLen; 16 | uint32 paramMaxLen; 17 | uint32 dataOffset; //offset of datatable + dataOffset 18 | } indices[num_entries]; 19 | FSeek(key_table_start); -------------------------------------------------------------------------------- /PkgEditor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace PkgEditor 7 | { 8 | static class Program 9 | { 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | Application.Run(new MainWin()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CollisionResolver.bt: -------------------------------------------------------------------------------- 1 | LittleEndian(); 2 | 3 | typedef struct { 4 | local int64 pos = FTell(); 5 | uint inode; 6 | uint type; 7 | uint namelen; 8 | uint entsize; 9 | char name[namelen]; 10 | char pad[entsize - (FTell() - pos)]; 11 | } Entry; 12 | 13 | while (!FEof()) 14 | { 15 | Entry e; 16 | if(ReadInt() == 0) 17 | { 18 | while(!FEof()){ 19 | FSeek(FTell() + 4); 20 | if(ReadInt() != 0) 21 | { 22 | break; 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /PkgEditor/PasscodeEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace PkgEditor 12 | { 13 | public partial class PasscodeEntry : Form 14 | { 15 | public PasscodeEntry(string prompt = "Please enter the package's passcode.", int length = 32) 16 | { 17 | InitializeComponent(); 18 | label1.Text = prompt; 19 | textBox1.MaxLength = length; 20 | } 21 | public string Passcode => textBox1.Text; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PFSC.bt: -------------------------------------------------------------------------------- 1 | LittleEndian(); 2 | char Magic[4]; 3 | int unk; // zero 4 | int unk; // 2 or 6 5 | int blocksz; 6 | uint64 blocksz2; // always the same as blocksz? 7 | uint64 block_offsets; // start of block offsets 8 | uint64 data_start; // start of compressed sectors 9 | uint64 data_length; // uncompressed image size 10 | 11 | FSeek(block_offsets); 12 | local uint64 num_blocks = data_length / blocksz; 13 | uint64 blocks[num_blocks + 1]; 14 | 15 | FSeek(blocks[0]); 16 | local uint64 i = 0; 17 | struct { 18 | for(i = 0; i < num_blocks; i++) 19 | struct { 20 | byte data[blocks[i + 1] - blocks[i]]; 21 | } sector; 22 | } sectors; -------------------------------------------------------------------------------- /rifa.bt: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | #include "Rif.bt" 3 | } Rif; 4 | 5 | string approx_date(int64 time) { 6 | string ret; 7 | local int64 secs_per_year = 31557600; 8 | local int64 secs_per_month = secs_per_year / 12; 9 | SPrintf(ret, "%4d-%02d", (time / secs_per_year)+1970, (time / secs_per_month) % 12 + 1); 10 | return ret; 11 | } 12 | 13 | string RIF_READ(Rif &r) { 14 | string ret; 15 | SPrintf(ret, "V%d T%x U%d C%d %s - %s", 16 | r.version, 17 | r.license_type, 18 | r.unk_flag, 19 | r.content_type, 20 | approx_date(r.startTime), 21 | r.contentId); 22 | return ret; 23 | } 24 | 25 | BigEndian(); 26 | if (ReadInt() == 0x72696661) { 27 | char rifaMagic[4]; 28 | char ServiceID[0x14]; 29 | FSeek(0x400); 30 | while(!FEof()) { 31 | Rif rif; 32 | } 33 | } -------------------------------------------------------------------------------- /PkgEditor/LogWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | using System.IO; 10 | 11 | namespace PkgEditor 12 | { 13 | public partial class LogWindow : Form 14 | { 15 | public LogWindow() 16 | { 17 | InitializeComponent(); 18 | writer = new LogWriter(logBox); 19 | } 20 | private TextWriter writer; 21 | 22 | private class LogWriter : TextWriter 23 | { 24 | private TextBox box; 25 | public LogWriter(TextBox b) { box = b; } 26 | public override void Write(char value) 27 | { 28 | box.Text += value; 29 | } 30 | public override void Write(string value) 31 | { 32 | box.Text += value; 33 | } 34 | public override Encoding Encoding 35 | { 36 | get { return Encoding.ASCII; } 37 | } 38 | } 39 | 40 | public TextWriter GetWriter() => writer; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /PkgEditor/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PkgEditor.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LibOrbisPkg.Util 5 | { 6 | public static class DictionaryExtensions 7 | { 8 | public static V GetOrDefault(this Dictionary d, K key, V def = default(V)) 9 | { 10 | if (d.ContainsKey(key)) return d[key]; 11 | return def; 12 | } 13 | } 14 | 15 | public static class ArrayExtensions 16 | { 17 | public static T[] Fill(this T[] arr, T val) 18 | { 19 | for (var i = 0; i < arr.Length; i++) 20 | { 21 | arr[i] = val; 22 | } 23 | return arr; 24 | } 25 | } 26 | 27 | public static class ByteArrayExtensions 28 | { 29 | public static string ToHexCompact(this byte[] b) 30 | { 31 | var sb = new System.Text.StringBuilder(); 32 | foreach (var x in b) sb.AppendFormat("{0:X2}", x); 33 | return sb.ToString(); 34 | } 35 | } 36 | #if !CORE 37 | public static class TupleExtension 38 | { 39 | public static void Deconstruct(this Tuple twople, out T1 item1, out T2 item2) 40 | { 41 | item1 = twople.Item1; 42 | item2 = twople.Item2; 43 | } 44 | } 45 | #endif 46 | } 47 | -------------------------------------------------------------------------------- /LibOrbisPkg/Rif/LicenseInfo.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using LibOrbisPkg.PKG; 4 | using LibOrbisPkg.Util; 5 | 6 | namespace LibOrbisPkg.Rif 7 | { 8 | class LicenseInfo 9 | { 10 | public LicenseInfo(string contentId, ContentType type, byte[] entitlement) 11 | { 12 | ContentId = contentId; 13 | ContentType = type; 14 | EntitlementKey = entitlement; 15 | Unknown_40 = ContentType == ContentType.AL ? 1 : 0; 16 | Unknown_48 = 0; 17 | Unknown_4C = 1; 18 | } 19 | public string ContentId; 20 | public byte[] EntitlementKey; 21 | public int Unknown_40; 22 | public ContentType ContentType = ContentType.AC; 23 | public int Unknown_48; 24 | public int Unknown_4C; 25 | } 26 | 27 | class LicenseInfoWriter : WriterBase 28 | { 29 | public LicenseInfoWriter(Stream stream) : base(true, stream) { } 30 | 31 | public void Write(LicenseInfo dat) 32 | { 33 | Write(Encoding.ASCII.GetBytes(dat.ContentId)); 34 | Write(new byte[12]); 35 | Write(dat.EntitlementKey ?? new byte[16]); 36 | Write(dat.Unknown_40); 37 | Write((int)dat.ContentType); 38 | Write(dat.Unknown_48); 39 | Write(dat.Unknown_4C); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PkgEditor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PkgEditor")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PkgEditor")] 13 | [assembly: AssemblyCopyright("Copyright © 2018-2020 Maxton")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("60b5d407-d849-4b84-8a5e-d740ea1f8125")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.2.0.0")] 36 | [assembly: AssemblyFileVersion("0.2.0.0")] 37 | -------------------------------------------------------------------------------- /LibOrbisPkg/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LibOrbisPkg")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LibOrbisPkg")] 13 | [assembly: AssemblyCopyright("Copyright © 2018-2020 Maxton")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b053f491-ff0f-4cbb-b03b-520591bb0441")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.2.0.0")] 36 | [assembly: AssemblyFileVersion("0.2.0.0")] 37 | -------------------------------------------------------------------------------- /LibOrbisPkg.Core.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibOrbisPkg.Core", "LibOrbisPkg.Core\LibOrbisPkg.Core.csproj", "{DA387D16-5ADB-4D8E-8B6C-B53BA6DCEF78}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PkgTool.Core", "PkgTool.Core\PkgTool.Core.csproj", "{296B3186-3C4A-490B-B33F-76BF62953020}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {DA387D16-5ADB-4D8E-8B6C-B53BA6DCEF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {DA387D16-5ADB-4D8E-8B6C-B53BA6DCEF78}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {DA387D16-5ADB-4D8E-8B6C-B53BA6DCEF78}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {DA387D16-5ADB-4D8E-8B6C-B53BA6DCEF78}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {296B3186-3C4A-490B-B33F-76BF62953020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {296B3186-3C4A-490B-B33F-76BF62953020}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {296B3186-3C4A-490B-B33F-76BF62953020}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {296B3186-3C4A-490B-B33F-76BF62953020}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C8FE18B1-9D06-4955-95C8-430BD68892DB} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /PkgEditor/Views/View.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace PkgEditor.Views 8 | { 9 | public class View : UserControl 10 | { 11 | /// 12 | /// A reference to the main window owning this view control. 13 | /// 14 | public MainWin mainWin; 15 | 16 | /// 17 | /// The Main window should subscribe to this event to get notified when the document is modified. 18 | /// 19 | public event EventHandler SaveStatusChanged; 20 | 21 | /// 22 | /// This method should be called by an overloading class when the document has been modified, so the UI can update the Save/As buttons. 23 | /// 24 | protected void OnSaveStatusChanged() => SaveStatusChanged?.Invoke(this, new EventArgs()); 25 | 26 | /// 27 | /// This should return true if the current document can be File->saved with Ctrl-S. 28 | /// 29 | public virtual bool CanSave { get; } 30 | 31 | /// 32 | /// This should return true if the current document can be File->save-as'd with Ctrl-Shift-S. 33 | /// 34 | public virtual bool CanSaveAs { get; } 35 | 36 | /// 37 | /// This method is called when the user presses Ctrl-S or clicks File->Save 38 | /// 39 | public virtual void Save() { } 40 | 41 | /// 42 | /// This method is called when the user presses Ctrl-Shift-S or clicks File->Save As... 43 | /// 44 | public virtual void SaveAs() { } 45 | 46 | /// 47 | /// This method is called when the user presse Ctrl-W or clicks File->Close 48 | /// 49 | public virtual void Close() { } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/PFSCWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using LibOrbisPkg.Util; 3 | 4 | namespace LibOrbisPkg.PFS 5 | { 6 | /// 7 | /// Writes a PFSC header to to a stream. Doesn't actually do compression or anything interesting. 8 | /// 9 | class PFSCWriter 10 | { 11 | const int BlockSize = 0x10000; 12 | /* 13 | * PFSC Header 14 | * NUM_BLOCKS = CEIL(size / BLOCK_SZ) 15 | * 0x000 : PFSC Magic (4 bytes) 16 | * 0x004 : Unknown (8 bytes) 17 | * 0x00C : Block Size (4 bytes) 18 | * 0x010 : Block Size (8 bytes) 19 | * 0x018 : Block offsets pointer (4 bytes) 20 | * 0x020 : Data start (8 bytes) 21 | * 0x028 : Data length (8 bytes) 22 | * 0x400 : Blocks (8 bytes * NUM_BLOCKS) 23 | * 0x10000 : Data (variable) 24 | */ 25 | private long num_blocks; 26 | public PFSCWriter(long size) 27 | { 28 | num_blocks = (size + BlockSize - 1) / BlockSize; 29 | var pointer_table_size = 8 + num_blocks * 8; 30 | var additional_pointer_blocks = ((pointer_table_size - 0xFC00) + 0xFFFF) / 0x10000; 31 | HeaderSize = 0x10000 + (additional_pointer_blocks > 0 ? BlockSize * additional_pointer_blocks : 0); 32 | } 33 | 34 | public readonly long HeaderSize; 35 | public void WritePFSCHeader(Stream s) 36 | { 37 | var start = s.Position; 38 | s.WriteInt32BE(0x50465343); // 'PFSC' 39 | s.WriteLE(0); 40 | s.WriteLE(6); 41 | s.WriteLE(BlockSize); 42 | s.WriteLE((long)BlockSize); 43 | s.WriteLE(0x400L); 44 | s.WriteLE(HeaderSize); 45 | s.WriteLE(num_blocks * BlockSize); 46 | s.Position = start + 0x400L; 47 | for(long i = 0; i <= num_blocks; i++) 48 | { 49 | s.WriteLE(HeaderSize + (i * BlockSize)); 50 | } 51 | s.Position = start + HeaderSize; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /PkgEditor/Views/PFSView.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PkgEditor.Views 2 | { 3 | partial class PFSView 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.fileView1 = new PkgEditor.Views.FileView(); 32 | this.SuspendLayout(); 33 | // 34 | // fileView1 35 | // 36 | this.fileView1.Dock = System.Windows.Forms.DockStyle.Fill; 37 | this.fileView1.Location = new System.Drawing.Point(0, 0); 38 | this.fileView1.Name = "fileView1"; 39 | this.fileView1.Size = new System.Drawing.Size(557, 354); 40 | this.fileView1.TabIndex = 0; 41 | // 42 | // PFSView 43 | // 44 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 45 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 46 | this.Controls.Add(this.fileView1); 47 | this.Name = "PFSView"; 48 | this.Size = new System.Drawing.Size(557, 354); 49 | this.ResumeLayout(false); 50 | 51 | } 52 | 53 | #endregion 54 | 55 | private FileView fileView1; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LibOrbisPkg/PlayGo/Manifest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Xml.Serialization; 3 | 4 | namespace LibOrbisPkg.PlayGo 5 | { 6 | [XmlRoot(ElementName = "psproject")] 7 | class Manifest 8 | { 9 | [XmlAttribute("fmt")] 10 | public string Format; 11 | [XmlAttribute("version")] 12 | public string version; 13 | [XmlElement(ElementName = "chunk_info")] 14 | public GP4.ChunkInfo chunk_info; 15 | 16 | 17 | public static void WriteTo(Manifest proj, System.IO.Stream s) 18 | { 19 | XmlSerializer mySerializer = new XmlSerializer(typeof(Manifest)); 20 | mySerializer.Serialize(s, proj); 21 | } 22 | 23 | public static Manifest ReadFrom(System.IO.Stream s) 24 | { 25 | XmlSerializer mySerializer = new XmlSerializer(typeof(Manifest)); 26 | var proj = (Manifest)mySerializer.Deserialize(s); 27 | return proj; 28 | } 29 | 30 | public static Manifest FromProject(GP4.Gp4Project project) 31 | { 32 | var man = new Manifest 33 | { 34 | Format = "playgo-manifest", 35 | version = "0990", 36 | chunk_info = project.volume.chunk_info 37 | }; 38 | man.chunk_info.chunks = null; 39 | return man; 40 | } 41 | 42 | public static byte[] Default = 43 | ("\r\n" + 44 | "\r\n" + 45 | " \r\n" + 46 | " \r\n" + 47 | " \r\n" + 48 | " 0\r\n" + 49 | " \r\n" + 50 | " \r\n" + 51 | " \r\n" + 52 | "\r\n").Select(x => (byte)x).ToArray(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /PkgEditor/Views/ObjectView.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PkgEditor.Views 2 | { 3 | partial class ObjectView 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.treeView1 = new System.Windows.Forms.TreeView(); 32 | this.SuspendLayout(); 33 | // 34 | // treeView1 35 | // 36 | this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill; 37 | this.treeView1.Location = new System.Drawing.Point(0, 0); 38 | this.treeView1.Name = "treeView1"; 39 | this.treeView1.Size = new System.Drawing.Size(150, 150); 40 | this.treeView1.TabIndex = 0; 41 | // 42 | // ObjectView 43 | // 44 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 45 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 46 | this.Controls.Add(this.treeView1); 47 | this.Name = "ObjectView"; 48 | this.ResumeLayout(false); 49 | 50 | } 51 | 52 | #endregion 53 | 54 | private System.Windows.Forms.TreeView treeView1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PkgEditor/LogWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PkgEditor 2 | { 3 | partial class LogWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.logBox = new System.Windows.Forms.TextBox(); 32 | this.SuspendLayout(); 33 | // 34 | // logBox 35 | // 36 | this.logBox.Dock = System.Windows.Forms.DockStyle.Fill; 37 | this.logBox.Location = new System.Drawing.Point(0, 0); 38 | this.logBox.Multiline = true; 39 | this.logBox.Name = "logBox"; 40 | this.logBox.ReadOnly = true; 41 | this.logBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 42 | this.logBox.Size = new System.Drawing.Size(575, 257); 43 | this.logBox.TabIndex = 0; 44 | // 45 | // LogWindow 46 | // 47 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 48 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 49 | this.ClientSize = new System.Drawing.Size(575, 257); 50 | this.Controls.Add(this.logBox); 51 | this.Name = "LogWindow"; 52 | this.ShowIcon = false; 53 | this.Text = "LogWindow"; 54 | this.ResumeLayout(false); 55 | this.PerformLayout(); 56 | 57 | } 58 | 59 | #endregion 60 | 61 | public System.Windows.Forms.TextBox logBox; 62 | } 63 | } -------------------------------------------------------------------------------- /PlaygoChunkDat.bt: -------------------------------------------------------------------------------- 1 | LittleEndian(); 2 | 3 | char magic[4]; 4 | uint16 version_major; 5 | uint16 version_minor; 6 | uint16 image_count; 7 | uint16 chunk_count; 8 | uint16 mchunk_count; 9 | uint16 scenario_count; 10 | uint32 file_size; 11 | uint16 default_scenario_id; 12 | uint16 attrib; 13 | uint32 sdk_ver; 14 | uint16 disc_count; 15 | uint16 layer_bmp; 16 | char reserved[32]; 17 | char content_id[48]; 18 | 19 | FSeek(0xC0); 20 | uint32 chunk_attrs_offset; 21 | uint32 chunk_attrs_size; 22 | 23 | uint32 chunk_mchunks_offset; 24 | uint32 chunk_mchunks_size; 25 | 26 | uint32 chunk_labels_offset; 27 | uint32 chunk_labels_size; 28 | 29 | uint32 mchunk_attrs_offset; 30 | uint32 mchunk_attrs_size; 31 | 32 | uint32 scenario_attrs_offset; 33 | uint32 scenario_attrs_size; 34 | 35 | uint32 scenario_chunks_offset; 36 | uint32 scenario_chunks_size; 37 | 38 | uint32 scenario_labels_offset; 39 | uint32 scenario_labels_size; 40 | 41 | uint32 inner_mchunk_attrs_offset; 42 | uint32 inner_mchunk_attrs_size; 43 | 44 | FSeek(chunk_attrs_offset); 45 | struct { 46 | byte flag; 47 | byte image_disc_layer_no; 48 | byte req_locus; 49 | FSeek(FTell() + 0xB); 50 | uint16 mchunk_count; 51 | uint64 language_mask; 52 | uint32 mchunks_offset; 53 | uint32 label_offset; 54 | } chunk_attrs[chunk_count]; 55 | FSeek(chunk_mchunks_offset); 56 | uint16 chunk_mchunks[chunk_mchunks_size/2]; 57 | FSeek(chunk_labels_offset); 58 | char chunk_labels[chunk_labels_size]; 59 | 60 | typedef struct { 61 | uint64 offset; 62 | uint64 size; 63 | } mchunk_attr; 64 | 65 | FSeek(mchunk_attrs_offset); 66 | mchunk_attr mchunk_attrs[mchunk_count]; 67 | 68 | FSeek(inner_mchunk_attrs_offset); 69 | mchunk_attr inner_mchunk_attrs[mchunk_count]; 70 | 71 | 72 | FSeek(scenario_attrs_offset); 73 | struct { 74 | byte type; 75 | FSeek(0x13 + FTell()); 76 | uint16 initial_chunk_count; 77 | uint16 chunk_count; 78 | uint32 chunks_offset; 79 | uint32 label_offset; 80 | } scenario_attrs[scenario_count]; 81 | 82 | FSeek(scenario_chunks_offset); 83 | uint16 scenario_chunks[scenario_chunks_size/2]; 84 | 85 | FSeek(scenario_labels_offset); 86 | char scenario_labels[scenario_labels_size]; -------------------------------------------------------------------------------- /PkgEditor/Views/PFSView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using LibOrbisPkg.PFS; 11 | using LibOrbisPkg.Util; 12 | using System.IO.MemoryMappedFiles; 13 | 14 | namespace PkgEditor.Views 15 | { 16 | public partial class PFSView : View 17 | { 18 | private MemoryMappedFile pfsFile; 19 | private MemoryMappedViewAccessor va; 20 | private PfsReader reader; 21 | public PFSView(string filename) 22 | { 23 | InitializeComponent(); 24 | pfsFile = MemoryMappedFile.CreateFromFile(filename, System.IO.FileMode.Open, mapName: null, 0, MemoryMappedFileAccess.Read); 25 | va = pfsFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); 26 | va.Read(0, out int val); 27 | if (val == PFSCReader.Magic) 28 | reader = new PfsReader(new PFSCReader(va)); 29 | else 30 | { 31 | PfsHeader header; 32 | using (var h = pfsFile.CreateViewStream(0, 0x600, MemoryMappedFileAccess.Read)) 33 | { 34 | header = PfsHeader.ReadFromStream(h); 35 | } 36 | byte[] tweak = null, data = null; 37 | if (header.Mode.HasFlag(PfsMode.Encrypted)) 38 | { 39 | var passcode = new PasscodeEntry("Please enter data key", 32); 40 | passcode.Text = "PFS is encrypted"; 41 | passcode.ShowDialog(); 42 | data = passcode.Passcode.FromHexCompact(); 43 | 44 | passcode = new PasscodeEntry("Please enter tweak key", 32); 45 | passcode.Text = "PFS is encrypted"; 46 | passcode.ShowDialog(); 47 | data = passcode.Passcode.FromHexCompact(); 48 | reader = new PfsReader(va, data: data, tweak: tweak); 49 | } 50 | else 51 | { 52 | reader = new PfsReader(va); 53 | } 54 | } 55 | fileView1.AddRoot(reader, filename); 56 | } 57 | 58 | public override void Close() 59 | { 60 | va.Dispose(); 61 | pfsFile.Dispose(); 62 | base.Close(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/OffsetStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace LibOrbisPkg.Util 4 | { 5 | /// 6 | /// A stream that acts as a window to another stream at the given offset. 7 | /// Seek operations will be relative to the offset. Size operations will 8 | /// pretend any part of the stream previous doesn't exist. 9 | /// 10 | public class OffsetStream : Stream 11 | { 12 | /// 13 | /// Creates an offset stream. Does not own the parent stream. 14 | /// 15 | /// 16 | /// 17 | public OffsetStream(Stream s, long offset) 18 | { 19 | src = s; 20 | if(src.Length < offset) 21 | { 22 | src.SetLength(offset); 23 | } 24 | this.offset = offset; 25 | } 26 | private Stream src; 27 | private long offset; 28 | 29 | public override bool CanRead => src.CanRead; 30 | 31 | public override bool CanSeek => src.CanSeek; 32 | 33 | public override bool CanWrite => src.CanWrite; 34 | 35 | public override long Length => src.Length - offset; 36 | 37 | public override long Position { get => src.Position - offset; set => src.Position = value + offset; } 38 | 39 | public override void Flush() => src.Flush(); 40 | 41 | public override int Read(byte[] buffer, int offset, int count) 42 | { 43 | return src.Read(buffer, offset, count); 44 | } 45 | 46 | public override long Seek(long where, SeekOrigin origin) 47 | { 48 | switch (origin) 49 | { 50 | case SeekOrigin.Begin: 51 | src.Seek(where + offset, origin); 52 | break; 53 | case SeekOrigin.Current: 54 | src.Seek(where, origin); 55 | break; 56 | case SeekOrigin.End: 57 | src.Seek(where, origin); 58 | break; 59 | default: 60 | break; 61 | } 62 | return Position; 63 | } 64 | 65 | public override void SetLength(long value) 66 | { 67 | src.SetLength(value + offset); 68 | } 69 | 70 | public override void Write(byte[] buffer, int offset, int count) 71 | { 72 | src.Write(buffer, offset, count); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /LibOrbisPkg.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibOrbisPkg", "LibOrbisPkg\LibOrbisPkg.csproj", "{B053F491-FF0F-4CBB-B03B-520591BB0441}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkgEditor", "PkgEditor\PkgEditor.csproj", "{60B5D407-D849-4B84-8A5E-D740EA1F8125}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | Release-minimal|Any CPU = Release-minimal|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Release-minimal|Any CPU.ActiveCfg = Release|Any CPU 22 | {B053F491-FF0F-4CBB-B03B-520591BB0441}.Release-minimal|Any CPU.Build.0 = Release|Any CPU 23 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Release-minimal|Any CPU.ActiveCfg = Release|Any CPU 28 | {60B5D407-D849-4B84-8A5E-D740EA1F8125}.Release-minimal|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {37BE3247-7A80-4D43-B59B-008F49D5CCE3} 35 | EndGlobalSection 36 | GlobalSection(Performance) = preSolution 37 | HasPerformanceSessions = true 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/ReaderBase.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace LibOrbisPkg.Util 5 | { 6 | public class ReaderBase 7 | { 8 | [StructLayout(LayoutKind.Explicit, Size = 8)] 9 | private unsafe struct storage 10 | { 11 | [FieldOffset(0)] 12 | public byte u8; 13 | [FieldOffset(0)] 14 | public sbyte s8; 15 | [FieldOffset(0)] 16 | public ushort u16; 17 | [FieldOffset(0)] 18 | public short s16; 19 | [FieldOffset(0)] 20 | public uint u32; 21 | [FieldOffset(0)] 22 | public int s32; 23 | [FieldOffset(0)] 24 | public ulong u64; 25 | [FieldOffset(0)] 26 | public long s64; 27 | [FieldOffset(0)] 28 | public float f32; 29 | [FieldOffset(0)] 30 | public double f64; 31 | [FieldOffset(0)] 32 | public fixed byte buf[8]; 33 | } 34 | 35 | private storage buffer; 36 | protected Stream s; 37 | protected bool flipEndian; 38 | protected ReaderBase(bool flipEndian, Stream stream) 39 | { 40 | s = stream; 41 | this.flipEndian = flipEndian; 42 | } 43 | private ref storage ReadEndian(int count) 44 | { 45 | unsafe 46 | { 47 | if (flipEndian) 48 | for (int i = count - 1 ; i >= 0; i--) buffer.buf[i] = (byte)s.ReadByte(); 49 | else 50 | for (int i = 0; i < count; i++) buffer.buf[i] = (byte)s.ReadByte(); 51 | } 52 | return ref buffer; 53 | } 54 | protected byte Byte() => ReadEndian(1).u8; 55 | protected sbyte SByte() => ReadEndian(1).s8; 56 | protected ushort UShort() => ReadEndian(2).u16; 57 | protected short Short() => ReadEndian(2).s16; 58 | protected uint UInt() => ReadEndian(4).u32; 59 | protected int Int() => ReadEndian(4).s32; 60 | protected ulong ULong() => ReadEndian(8).u64; 61 | protected long Long() => ReadEndian(8).s64; 62 | //protected unsafe void ReadBytes(byte* arr, int count) 63 | //{ 64 | // for (var i = 0; i < count; i++) 65 | // { 66 | // arr[i] = (byte)s.ReadByte(); 67 | // } 68 | //} 69 | protected byte[] ReadBytes(int count) 70 | { 71 | var ret = new byte[count]; 72 | s.Read(ret, 0, count); 73 | return ret; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/SubStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace LibOrbisPkg.Util 5 | { 6 | public class SubStream : Stream 7 | { 8 | public override bool CanRead => true; 9 | 10 | public override bool CanSeek => true; 11 | 12 | public override bool CanWrite => false; 13 | 14 | public override long Length { get; } 15 | 16 | public override long Position 17 | { 18 | get 19 | { 20 | return position; 21 | } 22 | 23 | set 24 | { 25 | Seek(value, SeekOrigin.Begin); 26 | } 27 | } 28 | 29 | /// 30 | /// Creates a non-owning read-only window into a stream 31 | /// 32 | public SubStream(Stream s, long offset, long length) 33 | { 34 | this.parent = s; 35 | this.offset = offset; 36 | Length = length; 37 | } 38 | 39 | private Stream parent; 40 | private long offset; 41 | private long position; 42 | 43 | public override int Read(byte[] buffer, int offset, int count) 44 | { 45 | parent.Seek(this.offset + Position, SeekOrigin.Begin); 46 | if (count + Position > Length) 47 | { 48 | count = (int)(Length - Position); 49 | } 50 | int bytes_read = parent.Read(buffer, offset, count); 51 | position += bytes_read; 52 | return bytes_read; 53 | } 54 | 55 | public override long Seek(long offset, SeekOrigin origin) 56 | { 57 | switch (origin) 58 | { 59 | case SeekOrigin.Begin: 60 | break; 61 | case SeekOrigin.Current: 62 | offset += position; 63 | break; 64 | case SeekOrigin.End: 65 | offset += Length; 66 | break; 67 | } 68 | if (offset > Length) 69 | { 70 | offset = Length; 71 | } 72 | else if (offset < 0) 73 | { 74 | offset = 0; 75 | } 76 | position = offset; 77 | return position; 78 | } 79 | 80 | #region Not Supported 81 | public override void Flush() 82 | { 83 | throw new NotSupportedException(); 84 | } 85 | public override void SetLength(long value) 86 | { 87 | throw new NotSupportedException(); 88 | } 89 | 90 | public override void Write(byte[] buffer, int offset, int count) 91 | { 92 | throw new NotSupportedException(); 93 | } 94 | #endregion 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /PkgEditor/ValidationDialog.cs: -------------------------------------------------------------------------------- 1 | using LibOrbisPkg.GP4; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace PkgEditor 13 | { 14 | public partial class ValidationDialog : Form 15 | { 16 | public ValidationDialog(List results = null) 17 | { 18 | InitializeComponent(); 19 | if (results != null) 20 | { 21 | fatalLabel.Visible = false; 22 | foreach (var error in results) 23 | { 24 | validateListBox1.Items.Add(error); 25 | if (error.Type == ValidateResult.ResultType.Fatal) 26 | { 27 | ignoreButton.Enabled = false; 28 | fatalLabel.Visible = true; 29 | } 30 | } 31 | } 32 | } 33 | 34 | private void ignoreButton_Click(object sender, EventArgs e) 35 | { 36 | DialogResult = DialogResult.Ignore; 37 | Close(); 38 | } 39 | } 40 | 41 | public class ValidateListBox : ListBox 42 | { 43 | public ValidateListBox() 44 | { 45 | DrawMode = DrawMode.OwnerDrawFixed; 46 | ItemHeight = 54; 47 | } 48 | protected override void OnDrawItem(DrawItemEventArgs e) 49 | { 50 | const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top | TextFormatFlags.WordBreak; 51 | 52 | e.DrawBackground(); 53 | if (Items.Count > e.Index && Items[e.Index] is ValidateResult v) 54 | { 55 | var penColor = v.Type == ValidateResult.ResultType.Fatal ? Brushes.Red : Brushes.Yellow; 56 | e.Graphics.FillRectangle(penColor, 2, e.Bounds.Y + 2, width: ItemHeight / 2 - 4, height: ItemHeight - 4); 57 | var textRect = e.Bounds; 58 | textRect.X += ItemHeight / 2; 59 | textRect.Width -= ItemHeight / 2; 60 | textRect.Height = ItemHeight / 3; 61 | string itemText = v.Type.ToString(); 62 | TextRenderer.DrawText(e.Graphics, itemText, new Font(e.Font, FontStyle.Bold), textRect, e.ForeColor, flags); 63 | 64 | textRect.Y += ItemHeight / 3; 65 | textRect.Height = 2 * ItemHeight / 3; 66 | itemText = DesignMode ? "Error Description" : v.Message; 67 | TextRenderer.DrawText(e.Graphics, itemText, e.Font, textRect, e.ForeColor, flags); 68 | } 69 | 70 | e.DrawFocusRectangle(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /LibOrbisPkg/PKG/PkgProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibOrbisPkg.PKG 4 | { 5 | /// 6 | /// Collection of data to use when building a PKG. 7 | /// 8 | public class PkgProperties 9 | { 10 | /// 11 | /// The volume type (should be one of the pkg_* types) 12 | /// 13 | public GP4.VolumeType VolumeType; 14 | 15 | /// 16 | /// 36 Character ID for the PKG 17 | /// 18 | public string ContentId; 19 | 20 | /// 21 | /// 32 Character Passcode 22 | /// 23 | public string Passcode; 24 | 25 | /// 26 | /// The volume timestamp. 27 | /// 28 | public DateTime TimeStamp; 29 | 30 | /// 31 | /// 32 Hex Character Entitlement Key (For AC, AL only) 32 | /// 33 | public string EntitlementKey; 34 | 35 | /// 36 | /// The creation date/time. Leave as default(DateTime) to disable 37 | /// 38 | public DateTime CreationDate; 39 | 40 | /// 41 | /// Set to true to use the creation time in addition to the date 42 | /// 43 | public bool UseCreationTime; 44 | 45 | /// 46 | /// The root of the directory tree for the PFS image. 47 | /// 48 | public PFS.FSDir RootDir; 49 | 50 | public static PkgProperties FromGp4(GP4.Gp4Project project, string projDir) 51 | { 52 | DateTime CreationDate; 53 | bool UseCreationTime = false; 54 | if ((project.volume.Package.CreationDate ?? "") == "") 55 | { 56 | CreationDate = default; 57 | } 58 | else if (project.volume.Package.CreationDate == "actual_datetime") 59 | { 60 | CreationDate = default; 61 | UseCreationTime = true; 62 | } 63 | else 64 | { 65 | var split = project.volume.Package.CreationDate.Split(' '); 66 | UseCreationTime = split.Length == 2; // Date and time specified 67 | CreationDate = DateTime.Parse(project.volume.Package.CreationDate).ToUniversalTime(); 68 | } 69 | return new PkgProperties 70 | { 71 | ContentId = project.volume.Package.ContentId, 72 | VolumeType = project.volume.Type, 73 | Passcode = project.volume.Package.Passcode, 74 | TimeStamp = project.volume.TimeStamp, 75 | EntitlementKey = project.volume.Package.EntitlementKey, 76 | CreationDate = CreationDate, 77 | UseCreationTime = UseCreationTime, 78 | RootDir = PFS.PfsProperties.BuildFSTree(project, projDir) 79 | }; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/WriterBase.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace LibOrbisPkg.Util 5 | { 6 | public class WriterBase 7 | { 8 | [StructLayout(LayoutKind.Explicit, Size = 8)] 9 | private unsafe struct storage 10 | { 11 | [FieldOffset(0)] 12 | public byte u8; 13 | [FieldOffset(0)] 14 | public sbyte s8; 15 | [FieldOffset(0)] 16 | public ushort u16; 17 | [FieldOffset(0)] 18 | public short s16; 19 | [FieldOffset(0)] 20 | public uint u32; 21 | [FieldOffset(0)] 22 | public int s32; 23 | [FieldOffset(0)] 24 | public ulong u64; 25 | [FieldOffset(0)] 26 | public long s64; 27 | [FieldOffset(0)] 28 | public float f32; 29 | [FieldOffset(0)] 30 | public double f64; 31 | [FieldOffset(0)] 32 | public fixed byte buf[8]; 33 | } 34 | 35 | private storage buffer; 36 | protected Stream s; 37 | protected bool flipEndian; 38 | protected WriterBase(bool flipEndian, Stream stream) 39 | { 40 | s = stream; 41 | this.flipEndian = flipEndian; 42 | } 43 | private void WriteEndian(int count) 44 | { 45 | unsafe 46 | { 47 | if (flipEndian) 48 | for (int i = count - 1; i >= 0; i--) s.WriteByte(buffer.buf[i]); 49 | else 50 | for (int i = 0; i < count; i++) s.WriteByte(buffer.buf[i]); 51 | } 52 | } 53 | protected void Write(byte b) => s.WriteByte(b); 54 | protected void Write(sbyte sb) => Write((byte)sb); 55 | protected void Write(ushort u) 56 | { 57 | buffer.u16 = u; 58 | WriteEndian(2); 59 | } 60 | protected void Write(short u) 61 | { 62 | buffer.s16 = u; 63 | WriteEndian(2); 64 | } 65 | protected void Write(uint u) 66 | { 67 | buffer.u32 = u; 68 | WriteEndian(4); 69 | } 70 | protected void Write(int u) 71 | { 72 | buffer.s32 = u; 73 | WriteEndian(4); 74 | } 75 | protected void Write(ulong u) 76 | { 77 | buffer.u64 = u; 78 | WriteEndian(8); 79 | } 80 | protected void Write(long u) 81 | { 82 | buffer.s64 = u; 83 | WriteEndian(8); 84 | } 85 | protected void Write(byte[] b) 86 | { 87 | if (b == null) 88 | return; 89 | s.Write(b, 0, b.Length); 90 | } 91 | //protected unsafe void Write(byte* b, int count) 92 | //{ 93 | // for (var i = 0; i < count; i++) 94 | // { 95 | // s.WriteByte(b[i]); 96 | // } 97 | //} 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PkgEditor/KeyDB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using System.Runtime.Serialization.Json; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace PkgEditor 11 | { 12 | /// 13 | /// Stores encryption keys in plain or hex-encoded strings. 14 | /// 15 | [DataContract] 16 | class KeyDB 17 | { 18 | [DataContract] 19 | public class XTSKey 20 | { 21 | [DataMember] 22 | public string Tweak { get; set; } 23 | [DataMember] 24 | public string Data { get; set; } 25 | } 26 | /// 27 | /// Plaintext passcodes indexed by Content ID 28 | /// 29 | [DataMember] 30 | public Dictionary Passcodes { get; set; } 31 | /// 32 | /// Compact hexadecimal encoded PFS encryption keys indexed by Content ID 33 | /// 34 | [DataMember] 35 | public Dictionary EKPFS { get; set; } 36 | /// 37 | /// Compact hexadecimal encoded XTS keys indexed by Content ID + first 4 bytes of PFS Image Digest 38 | /// 39 | [DataMember] 40 | public Dictionary XTS { get; set; } 41 | 42 | public void Save(string path = null) 43 | { 44 | if (path == null) path = FileName; 45 | using (var keydb = File.OpenWrite(path)) 46 | { 47 | var serializer = new DataContractJsonSerializer(typeof(KeyDB), new DataContractJsonSerializerSettings() 48 | { 49 | UseSimpleDictionaryFormat = true, 50 | }); 51 | serializer.WriteObject(keydb, this); 52 | } 53 | } 54 | private KeyDB() { 55 | Passcodes = new Dictionary(); 56 | EKPFS = new Dictionary(); 57 | XTS = new Dictionary(); 58 | } 59 | private static KeyDB instance = null; 60 | public static KeyDB Instance { get => instance != null ? instance : (instance = Load()); } 61 | public static string FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LibOrbisPkg", "keydb.json"); 62 | private static KeyDB Load() 63 | { 64 | if (!File.Exists(FileName)) 65 | { 66 | Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LibOrbisPkg")); 67 | return new KeyDB(); 68 | } 69 | using (var keydb = File.OpenRead(FileName)) 70 | { 71 | var serializer = new DataContractJsonSerializer(typeof(KeyDB), new DataContractJsonSerializerSettings() 72 | { 73 | UseSimpleDictionaryFormat = true 74 | }); 75 | return serializer.ReadObject(keydb) as KeyDB; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.sln text eol=crlf 8 | 9 | ############################################################################### 10 | # Set default behavior for command prompt diff. 11 | # 12 | # This is need for earlier builds of msysgit that does not have it on by 13 | # default for csharp files. 14 | # Note: This is only used by command line 15 | ############################################################################### 16 | #*.cs diff=csharp 17 | 18 | ############################################################################### 19 | # Set the merge driver for project and solution files 20 | # 21 | # Merging from the command prompt will add diff markers to the files if there 22 | # are conflicts (Merging from VS is not affected by the settings below, in VS 23 | # the diff markers are never inserted). Diff markers may cause the following 24 | # file extensions to fail to load in VS. An alternative would be to treat 25 | # these files as binary and thus will always conflict and require user 26 | # intervention with every merge. To do so, just uncomment the entries below 27 | ############################################################################### 28 | #*.sln merge=binary 29 | #*.csproj merge=binary 30 | #*.vbproj merge=binary 31 | #*.vcxproj merge=binary 32 | #*.vcproj merge=binary 33 | #*.dbproj merge=binary 34 | #*.fsproj merge=binary 35 | #*.lsproj merge=binary 36 | #*.wixproj merge=binary 37 | #*.modelproj merge=binary 38 | #*.sqlproj merge=binary 39 | #*.wwaproj merge=binary 40 | 41 | ############################################################################### 42 | # behavior for image files 43 | # 44 | # image files are treated as binary by default. 45 | ############################################################################### 46 | #*.jpg binary 47 | #*.png binary 48 | #*.gif binary 49 | 50 | ############################################################################### 51 | # diff behavior for common document formats 52 | # 53 | # Convert binary document formats to text before diffing them. This feature 54 | # is only available from the command line. Turn it on by uncommenting the 55 | # entries below. 56 | ############################################################################### 57 | #*.doc diff=astextplain 58 | #*.DOC diff=astextplain 59 | #*.docx diff=astextplain 60 | #*.DOCX diff=astextplain 61 | #*.dot diff=astextplain 62 | #*.DOT diff=astextplain 63 | #*.pdf diff=astextplain 64 | #*.PDF diff=astextplain 65 | #*.rtf diff=astextplain 66 | #*.RTF diff=astextplain 67 | -------------------------------------------------------------------------------- /PkgEditor/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PkgEditor.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PkgEditor.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/MersenneTwister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibOrbisPkg.Util 4 | { 5 | /// 6 | /// Mersenne Twister PRNG 7 | /// 8 | public class MersenneTwister 9 | { 10 | public const int N = 624; 11 | const uint M = 397; 12 | const uint DefaultSeed = 0x12BD6AA; 13 | const uint MatrixA = 0x9908b0df; 14 | const uint UpperMask = 0x80000000; 15 | const uint LowerMask = 0x7fffffff; 16 | const uint Constant1 = 0x6C078965; 17 | const uint Constant2 = 0x19660D; 18 | const uint Constant3 = 0x5D588B65; 19 | const uint Constant4 = 0x9d2c5680; 20 | const uint Constant5 = 0xefc60000; 21 | 22 | public uint[] mt = new uint[N]; 23 | uint Mask(int val) => ~((~0u) << val); 24 | uint TwoToThe(int val) => 1u << val; 25 | 26 | public MersenneTwister(uint seed = DefaultSeed) 27 | { 28 | mt[0] = seed; 29 | for (mti = 1; mti < N; mti++) 30 | { 31 | mt[mti] = mti + Constant1 * (mt[mti - 1] ^ (mt[mti - 1] >> 30)); 32 | } 33 | } 34 | 35 | public MersenneTwister(uint[] seed) : this(DefaultSeed) 36 | { 37 | uint stateIdx = 1, seedIdx = 0; 38 | for (int length = Math.Max(N, seed.Length); length > 0; length--) 39 | { 40 | mt[stateIdx] = (mt[stateIdx] ^ ((mt[stateIdx - 1] ^ (mt[stateIdx - 1] >> 30)) * Constant2)) + seed[seedIdx] + seedIdx; 41 | stateIdx++; 42 | seedIdx++; 43 | if (stateIdx >= N) { mt[0] = mt[N - 1]; stateIdx = 1; } 44 | if (seedIdx >= seed.Length) seedIdx = 0; 45 | } 46 | for (int length = 0; length < N - 1; length++) 47 | { 48 | mt[stateIdx] = (mt[stateIdx] ^ ((mt[stateIdx - 1] ^ (mt[stateIdx - 1] >> 30)) * Constant3)) - stateIdx; 49 | stateIdx++; 50 | if (stateIdx >= N) { mt[0] = mt[N - 1]; stateIdx = 1; } 51 | } 52 | mt[0] = (1u << 31); /* MSB is 1; assuring non-zero initial array */ 53 | } 54 | 55 | uint mti = 0; 56 | public uint Int32() 57 | { 58 | var mag01 = new uint[] { 0, MatrixA }; 59 | uint y; 60 | if (mti >= N) 61 | { 62 | /* generate N words all at once */ 63 | uint kk; 64 | for (kk = 0; kk < N - M; kk++) 65 | { 66 | y = (mt[kk] & UpperMask) | (mt[kk + 1] & LowerMask); 67 | mt[kk] = mt[kk + M] ^ ((y >> 1) & Mask(31)) ^ mag01[y & 1]; 68 | } 69 | for (; kk < N - 1; kk++) 70 | { 71 | y = (mt[kk] & UpperMask) | (mt[kk + 1] & LowerMask); 72 | mt[kk] = mt[kk + M - N] ^ ((y >> 1) & Mask(31)) ^ mag01[y & 1]; 73 | } 74 | y = (mt[N - 1] & UpperMask) | (mt[0] & LowerMask); 75 | mt[N - 1] = mt[M - 1] ^ ((y >> 1) & Mask(31)) ^ mag01[y & 1]; 76 | mti = 0; 77 | } 78 | 79 | y = mt[mti++]; 80 | /* Tempering */ 81 | y ^= (y >> 11) & Mask(21); 82 | y ^= (y << 7) & Constant4; 83 | y ^= (y << 15) & Constant5; 84 | y ^= (y >> 18) & Mask(14); 85 | return y; 86 | } 87 | 88 | public uint Int31() => Int32() & Mask(31); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /LibOrbisPkg/Util/XtsBlockTransform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace LibOrbisPkg.Util 5 | { 6 | public class XtsBlockTransform 7 | { 8 | // Used on the plaintext XORed with the encrypted sector number 9 | private SymmetricAlgorithm cipher; 10 | // Used to encrypt the tweak 11 | private SymmetricAlgorithm tweakCipher; 12 | 13 | private byte[] tweak = new byte[16]; 14 | private byte[] xor = new byte[16]; 15 | private byte[] xor2 = new byte[16]; 16 | private byte[] encryptedTweak = new byte[16]; 17 | 18 | /// 19 | /// Creates an AES-XTS-128 transformer 20 | /// 21 | public XtsBlockTransform(byte[] dataKey, byte[] tweakKey) 22 | { 23 | cipher = new AesManaged 24 | { 25 | Mode = CipherMode.ECB, 26 | KeySize = 128, 27 | Key = dataKey, 28 | Padding = PaddingMode.None, 29 | BlockSize = 128, 30 | }; 31 | tweakCipher = new AesManaged 32 | { 33 | Mode = CipherMode.ECB, 34 | KeySize = 128, 35 | Key = tweakKey, 36 | Padding = PaddingMode.None, 37 | BlockSize = 128, 38 | }; 39 | } 40 | 41 | public void EncryptSector(byte[] sector, ulong sectorNum) => CryptSector(sector, sectorNum, true); 42 | public void DecryptSector(byte[] sector, ulong sectorNum) => CryptSector(sector, sectorNum, false); 43 | 44 | /// 45 | /// Encrypts or decrypts the given sector with XEX. 46 | /// 47 | /// Sector plain/ciphertext 48 | /// Sector index number 49 | /// If this is set to true, encrypt the sector 50 | public void CryptSector(byte[] sector, ulong sectorNum, bool encrypt = false) 51 | { 52 | // Reset tweak to sector number 53 | Buffer.BlockCopy(BitConverter.GetBytes(sectorNum), 0, tweak, 0, 8); 54 | for (int x = 8; x < 16; x++) 55 | tweak[x] = 0; 56 | using (var tweakEncryptor = tweakCipher.CreateEncryptor()) 57 | using (var cryptor = encrypt ? cipher.CreateEncryptor() : cipher.CreateDecryptor()) 58 | { 59 | tweakEncryptor.TransformBlock(tweak, 0, 16, encryptedTweak, 0); 60 | for (int destOffset = 0; destOffset < sector.Length; destOffset += 16) 61 | { 62 | for (var x = 0; x < 16; x++) 63 | { 64 | xor[x] = (byte)(sector[x + destOffset] ^ encryptedTweak[x]); 65 | } 66 | cryptor.TransformBlock(xor, 0, 16, xor, 0); 67 | for (var x = 0; x < 16; x++) 68 | { 69 | sector[x + destOffset] = (byte)(xor[x] ^ encryptedTweak[x]); 70 | } 71 | // GF-Multiply Tweak 72 | int feedback = 0; 73 | for (int k = 0; k < 16; k++) 74 | { 75 | byte tmp = encryptedTweak[k]; 76 | encryptedTweak[k] = (byte)(2 * encryptedTweak[k] | feedback); 77 | feedback = (tmp & 0x80) >> 7; 78 | } 79 | if (feedback != 0) 80 | encryptedTweak[0] ^= 0x87; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /PkgEditor/Views/ObjectView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | using System.Collections; 10 | 11 | namespace PkgEditor.Views 12 | { 13 | public partial class ObjectView : View 14 | { 15 | public override bool CanSave => false; 16 | public override bool CanSaveAs => false; 17 | 18 | public ObjectView() : this(null) { } 19 | public ObjectView(object obj) 20 | { 21 | InitializeComponent(); 22 | ObjectPreview(obj); 23 | } 24 | 25 | object previewObj; 26 | public void ObjectPreview(object obj) 27 | { 28 | treeView1.Nodes.Clear(); 29 | AddObjectNodes(obj, treeView1.Nodes); 30 | previewObj = obj; 31 | } 32 | 33 | /// 34 | /// Adds the given object's public fields to the given TreeNodeCollection. 35 | /// 36 | void AddObjectNodes(object obj, TreeNodeCollection nodes) 37 | { 38 | if (obj == null) return; 39 | var fields = obj.GetType().GetFields(); 40 | foreach (var f in fields) 41 | { 42 | if (f.IsLiteral) continue; 43 | 44 | var val = f.GetValue(obj); 45 | if (val is byte[] b) 46 | { 47 | nodes.Add(f.Name + " = " + LibOrbisPkg.Util.Crypto.AsHexCompact(b)); 48 | } 49 | else if (f.FieldType.IsPrimitive || f.FieldType == typeof(string) || f.FieldType.IsEnum) 50 | { 51 | if (val != null) 52 | { 53 | nodes.Add(f.Name + " = " + val.ToString()); 54 | } 55 | } 56 | else if (f.FieldType.IsArray) 57 | { 58 | AddArrayNodes(f.GetValue(obj) as Array, f.Name, nodes); 59 | } 60 | else if (f.FieldType.IsGenericType && f.FieldType.GetGenericTypeDefinition() == typeof(List<>)) 61 | { 62 | var internalType = f.FieldType.GetGenericArguments()[0]; 63 | AddArrayNodes((f.GetValue(obj) as IList).Cast().ToArray(), f.Name, nodes); 64 | } 65 | else 66 | { 67 | var node = new TreeNode(f.Name); 68 | AddObjectNodes(f.GetValue(obj), node.Nodes); 69 | nodes.Add(node); 70 | } 71 | } 72 | } 73 | 74 | /// 75 | /// Adds the given array to the given TreeNodeCollection. 76 | /// 77 | void AddArrayNodes(Array arr, string name, TreeNodeCollection nodes) 78 | { 79 | var node = new TreeNode($"{name} ({arr.Length})"); 80 | var eType = arr.GetType().GetElementType(); 81 | if (eType.IsPrimitive || eType == typeof(string) || eType.IsEnum) 82 | for (var i = 0; i < arr.Length; i++) 83 | { 84 | var n = new TreeNode($"{name}[{i}] = {arr.GetValue(i)}"); 85 | node.Nodes.Add(n); 86 | } 87 | else for (var i = 0; i < arr.Length; i++) 88 | { 89 | var myName = $"{name}[{i}]"; 90 | if (eType.IsArray) 91 | AddArrayNodes(arr.GetValue(i) as Array, myName, node.Nodes); 92 | else 93 | { 94 | var obj = arr.GetValue(i); 95 | 96 | System.Reflection.FieldInfo nameField; 97 | if (null != (nameField = obj.GetType().GetField("Name"))) 98 | { 99 | myName += $" (Name: {nameField.GetValue(obj)})"; 100 | } 101 | var n = new TreeNode(myName); 102 | var item = arr.GetValue(i); 103 | AddObjectNodes(item, n.Nodes); 104 | node.Nodes.Add(n); 105 | } 106 | } 107 | nodes.Add(node); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.2.{build} 2 | 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | image: Visual Studio 2019 9 | 10 | configuration: Release 11 | 12 | platform: Any CPU 13 | assembly_info: 14 | patch: true 15 | file: '**\AssemblyInfo.*' 16 | assembly_version: '{version}.0' 17 | assembly_file_version: '{version}.0' 18 | assembly_informational_version: '{version}.0' 19 | 20 | install: 21 | - cmd: git submodule update --init --recursive 22 | 23 | before_build: 24 | - cmd: nuget restore LibOrbisPkg.sln 25 | 26 | build: 27 | project: LibOrbisPkg.sln 28 | parallel: true 29 | verbosity: minimal 30 | 31 | after_build: 32 | - dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true PkgTool.Core 33 | - dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true PkgTool.Core 34 | - > 35 | 7z a PkgEditor-%APPVEYOR_BUILD_VERSION%.zip 36 | %APPVEYOR_BUILD_FOLDER%\PkgEditor\bin\Release\PkgEditor.exe 37 | %APPVEYOR_BUILD_FOLDER%\LibOrbisPkg\bin\Release\LibOrbisPkg.dll 38 | %APPVEYOR_BUILD_FOLDER%\README.md 39 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 40 | - > 41 | 7z a PkgTool-%APPVEYOR_BUILD_VERSION%.zip 42 | %APPVEYOR_BUILD_FOLDER%\PkgTool\bin\Release\PkgTool.exe 43 | %APPVEYOR_BUILD_FOLDER%\LibOrbisPkg\bin\Release\LibOrbisPkg.dll 44 | %APPVEYOR_BUILD_FOLDER%\README.md 45 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 46 | - > 47 | 7z a LibOrbisPkg-%APPVEYOR_BUILD_VERSION%.zip 48 | %APPVEYOR_BUILD_FOLDER%\LibOrbisPkg\bin\Release\LibOrbisPkg.dll 49 | %APPVEYOR_BUILD_FOLDER%\README.md 50 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 51 | - > 52 | 7z a PkgEditor.Core-%APPVEYOR_BUILD_VERSION%.zip 53 | %APPVEYOR_BUILD_FOLDER%\PkgEditor.Core\bin\Release\netcoreapp3.0\PkgEditor.Core.exe 54 | %APPVEYOR_BUILD_FOLDER%\PkgEditor.Core\bin\Release\netcoreapp3.0\PkgEditor.Core.dll 55 | %APPVEYOR_BUILD_FOLDER%\PkgEditor.Core\bin\Release\netcoreapp3.0\PkgEditor.Core.runtimeconfig.json 56 | %APPVEYOR_BUILD_FOLDER%\PkgEditor.Core\bin\Release\netcoreapp3.0\LibOrbisPkg.Core.dll 57 | %APPVEYOR_BUILD_FOLDER%\README.md 58 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 59 | - > 60 | 7z a PkgTool.Core-%APPVEYOR_BUILD_VERSION%.zip 61 | %APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Release\netcoreapp3.0\PkgTool.Core.exe 62 | %APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Release\netcoreapp3.0\PkgTool.Core.dll 63 | %APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Release\netcoreapp3.0\PkgTool.Core.runtimeconfig.json 64 | %APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Release\netcoreapp3.0\LibOrbisPkg.Core.dll 65 | %APPVEYOR_BUILD_FOLDER%\README.md 66 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 67 | - > 68 | 7z a PkgTool.Core-linux-x64-%APPVEYOR_BUILD_VERSION%.zip 69 | "%APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Any CPU\Release\netcoreapp3.0\linux-x64\publish\PkgTool.Core" 70 | %APPVEYOR_BUILD_FOLDER%\README.md 71 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 72 | - > 73 | 7z a PkgTool.Core-osx-x64-%APPVEYOR_BUILD_VERSION%.zip 74 | "%APPVEYOR_BUILD_FOLDER%\PkgTool.Core\bin\Any CPU\Release\netcoreapp3.0\osx-x64\publish\PkgTool.Core" 75 | %APPVEYOR_BUILD_FOLDER%\README.md 76 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 77 | - > 78 | 7z a LibOrbisPkg.Core-%APPVEYOR_BUILD_VERSION%.zip 79 | %APPVEYOR_BUILD_FOLDER%\LibOrbisPkg.Core\bin\Release\netcoreapp3.0\LibOrbisPkg.Core.dll 80 | %APPVEYOR_BUILD_FOLDER%\README.md 81 | %APPVEYOR_BUILD_FOLDER%\LICENSE.txt 82 | artifacts: 83 | - path: PkgEditor-%APPVEYOR_BUILD_VERSION%.zip 84 | - path: PkgTool-%APPVEYOR_BUILD_VERSION%.zip 85 | - path: LibOrbisPkg-%APPVEYOR_BUILD_VERSION%.zip 86 | - path: PkgEditor.Core-%APPVEYOR_BUILD_VERSION%.zip 87 | - path: PkgTool.Core-%APPVEYOR_BUILD_VERSION%.zip 88 | - path: LibOrbisPkg.Core-%APPVEYOR_BUILD_VERSION%.zip 89 | - path: PkgTool.Core-linux-x64-%APPVEYOR_BUILD_VERSION%.zip 90 | - path: PkgTool.Core-osx-x64-%APPVEYOR_BUILD_VERSION%.zip 91 | -------------------------------------------------------------------------------- /LibOrbisPkg/PKG/PkgWriter.cs: -------------------------------------------------------------------------------- 1 | using LibOrbisPkg.Util; 2 | using System.Linq; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace LibOrbisPkg.PKG 7 | { 8 | public class PkgWriter : Util.WriterBase 9 | { 10 | public PkgWriter(Stream s) : base(true, s) { } 11 | 12 | public void WriteBody(Pkg pkg, string contentId, string passcode) 13 | { 14 | foreach (var entry in pkg.Entries) 15 | { 16 | s.Position = entry.meta.DataOffset; 17 | if (entry.meta.Encrypted) 18 | { 19 | entry.WriteEncrypted(s, contentId, passcode); 20 | } 21 | else 22 | { 23 | entry.Write(s); 24 | } 25 | } 26 | } 27 | 28 | public void WriteHeader(in Header hdr) 29 | { 30 | s.Position = 0x00; 31 | Write(Encoding.ASCII.GetBytes(hdr.CNTMagic)); 32 | s.Position = 0x04; 33 | Write((uint)hdr.flags); 34 | s.Position = 0x08; 35 | Write(hdr.unk_0x08); 36 | s.Position = 0x0C; 37 | Write(hdr.unk_0x0C); /* 0xF */ 38 | s.Position = 0x10; 39 | Write(hdr.entry_count); 40 | s.Position = 0x14; 41 | Write(hdr.sc_entry_count); 42 | s.Position = 0x16; 43 | Write(hdr.entry_count_2); /* same as entry_count */ 44 | s.Position = 0x18; 45 | Write(hdr.entry_table_offset); 46 | s.Position = 0x1C; 47 | Write(hdr.main_ent_data_size); 48 | s.Position = 0x20; 49 | Write(hdr.body_offset); 50 | s.Position = 0x28; 51 | Write(hdr.body_size); 52 | s.Position = 0x40; 53 | Write(Encoding.ASCII.GetBytes(hdr.content_id)); // Length = PKG_CONTENT_ID_SIZE 54 | s.Position = 0x70; 55 | Write((uint)hdr.drm_type); 56 | s.Position = 0x74; 57 | Write((uint)hdr.content_type); 58 | s.Position = 0x78; 59 | Write((uint)hdr.content_flags); 60 | s.Position = 0x7C; 61 | Write(hdr.promote_size); 62 | s.Position = 0x80; 63 | Write(hdr.version_date); 64 | s.Position = 0x84; 65 | Write(hdr.version_hash); 66 | s.Position = 0x88; 67 | Write(hdr.unk_0x88); /* for delta patches only? */ 68 | s.Position = 0x8C; 69 | Write(hdr.unk_0x8C); /* for delta patches only? */ 70 | s.Position = 0x90; 71 | Write(hdr.unk_0x90); /* for delta patches only? */ 72 | s.Position = 0x94; 73 | Write(hdr.unk_0x94); /* for delta patches only? */ 74 | s.Position = 0x98; 75 | Write((uint)hdr.iro_tag); 76 | s.Position = 0x9C; 77 | Write(hdr.ekc_version); /* drm type version */ 78 | s.Position = 0x100; 79 | Write(hdr.sc_entries1_hash); 80 | s.Position = 0x120; 81 | Write(hdr.sc_entries2_hash); 82 | s.Position = 0x140; 83 | Write(hdr.digest_table_hash); 84 | s.Position = 0x160; 85 | Write(hdr.body_digest); 86 | 87 | // TODO: i think these fields are actually members of element of container array 88 | s.Position = 0x400; 89 | Write(hdr.unk_0x400); 90 | s.Position = 0x404; 91 | Write(hdr.pfs_image_count); 92 | s.Position = 0x408; 93 | Write(hdr.pfs_flags); 94 | s.Position = 0x410; 95 | Write(hdr.pfs_image_offset); 96 | s.Position = 0x418; 97 | Write(hdr.pfs_image_size); 98 | s.Position = 0x420; 99 | Write(hdr.mount_image_offset); 100 | s.Position = 0x428; 101 | Write(hdr.mount_image_size); 102 | s.Position = 0x430; 103 | Write(hdr.package_size); 104 | s.Position = 0x438; 105 | Write(hdr.pfs_signed_size); 106 | s.Position = 0x43C; 107 | Write(hdr.pfs_cache_size); 108 | s.Position = 0x440; 109 | Write(hdr.pfs_image_digest); 110 | s.Position = 0x460; 111 | Write(hdr.pfs_signed_digest); 112 | s.Position = 0x480; 113 | Write(hdr.pfs_split_size_nth_0); 114 | s.Position = 0x488; 115 | Write(hdr.pfs_split_size_nth_1); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PkgEditor/PasscodeEntry.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PkgEditor 2 | { 3 | partial class PasscodeEntry 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.textBox1 = new System.Windows.Forms.TextBox(); 32 | this.label1 = new System.Windows.Forms.Label(); 33 | this.button1 = new System.Windows.Forms.Button(); 34 | this.button2 = new System.Windows.Forms.Button(); 35 | this.SuspendLayout(); 36 | // 37 | // textBox1 38 | // 39 | this.textBox1.Location = new System.Drawing.Point(12, 35); 40 | this.textBox1.MaxLength = 32; 41 | this.textBox1.Name = "textBox1"; 42 | this.textBox1.Size = new System.Drawing.Size(373, 20); 43 | this.textBox1.TabIndex = 0; 44 | // 45 | // label1 46 | // 47 | this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 48 | | System.Windows.Forms.AnchorStyles.Right))); 49 | this.label1.Location = new System.Drawing.Point(12, 9); 50 | this.label1.Name = "label1"; 51 | this.label1.Size = new System.Drawing.Size(373, 23); 52 | this.label1.TabIndex = 1; 53 | this.label1.Text = "Please enter the package\'s passcode."; 54 | this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 55 | // 56 | // button1 57 | // 58 | this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; 59 | this.button1.Location = new System.Drawing.Point(161, 61); 60 | this.button1.Name = "button1"; 61 | this.button1.Size = new System.Drawing.Size(75, 23); 62 | this.button1.TabIndex = 2; 63 | this.button1.Text = "OK"; 64 | this.button1.UseVisualStyleBackColor = true; 65 | // 66 | // button2 67 | // 68 | this.button2.Location = new System.Drawing.Point(161, 90); 69 | this.button2.Name = "button2"; 70 | this.button2.Size = new System.Drawing.Size(75, 23); 71 | this.button2.TabIndex = 3; 72 | this.button2.Text = "Cancel"; 73 | this.button2.UseVisualStyleBackColor = true; 74 | // 75 | // PasscodeEntry 76 | // 77 | this.AcceptButton = this.button1; 78 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 79 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 80 | this.CancelButton = this.button2; 81 | this.ClientSize = new System.Drawing.Size(397, 127); 82 | this.Controls.Add(this.button2); 83 | this.Controls.Add(this.button1); 84 | this.Controls.Add(this.label1); 85 | this.Controls.Add(this.textBox1); 86 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 87 | this.Name = "PasscodeEntry"; 88 | this.Text = "PasscodeEntry"; 89 | this.ResumeLayout(false); 90 | this.PerformLayout(); 91 | 92 | } 93 | 94 | #endregion 95 | 96 | private System.Windows.Forms.TextBox textBox1; 97 | private System.Windows.Forms.Label label1; 98 | private System.Windows.Forms.Button button1; 99 | private System.Windows.Forms.Button button2; 100 | } 101 | } -------------------------------------------------------------------------------- /PkgEditor/Views/CryptoDebug.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using System.IO.MemoryMappedFiles; 11 | using LibOrbisPkg.Util; 12 | using LibOrbisPkg.PFS; 13 | 14 | namespace PkgEditor.Views 15 | { 16 | public partial class CryptoDebug : View 17 | { 18 | public CryptoDebug() 19 | { 20 | InitializeComponent(); 21 | pfsSeed.TextChanged += propagate_key; 22 | ekpfsInput.TextChanged += propagate_key; 23 | indexInput.TextChanged += propagate_key; 24 | 25 | dataKey.TextChanged += invalidateSector; 26 | tweakKey.TextChanged += invalidateSector; 27 | xtsSectorSize.TextChanged += invalidateSector; 28 | xtsStartSector.TextChanged += invalidateSector; 29 | } 30 | 31 | 32 | private void propagate_key(object sender, EventArgs e) 33 | { 34 | uint index = 1; 35 | uint.TryParse(indexInput.Text, out index); 36 | var keys = Crypto.PfsGenCryptoKey(ekpfsInput.Text.FromHexCompact(), pfsSeed.Text.FromHexCompact(), index); 37 | var data = new byte[16]; 38 | var tweak = new byte[16]; 39 | Buffer.BlockCopy(keys, 0, tweak, 0, 16); 40 | Buffer.BlockCopy(keys, 16, data, 0, 16); 41 | dataKey.Text = data.ToHexCompact(); 42 | tweakKey.Text = tweak.ToHexCompact(); 43 | } 44 | 45 | private MemoryMappedFile pfs; 46 | private IMemoryAccessor accessor; 47 | private IMemoryReader xtsReader; 48 | private PfsHeader header; 49 | void ClosePfs() 50 | { 51 | accessor?.Dispose(); 52 | pfs?.Dispose(); 53 | pfs = null; 54 | accessor = null; 55 | } 56 | 57 | void LoadPfs(string filename) 58 | { 59 | pfs = MemoryMappedFile.CreateFromFile(filename, System.IO.FileMode.Open, mapName: null, 0, MemoryMappedFileAccess.Read); 60 | accessor = new MemoryMappedViewAccessor_(pfs.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read), true); 61 | using (var s = pfs.CreateViewStream(0, 0x5A0, MemoryMappedFileAccess.Read)) 62 | { 63 | header = PfsHeader.ReadFromStream(s); 64 | objectView1.ObjectPreview(header); 65 | pfsSeed.Text = header.Seed.ToHexCompact(); 66 | } 67 | xtsReader = accessor; 68 | UpdateHexView(); 69 | } 70 | 71 | void UpdateHexView() 72 | { 73 | if (xtsReader == null) return; 74 | var buf = new byte[header.BlockSize]; 75 | xtsReader.Read(header.BlockSize, buf, 0, buf.Length); 76 | var sb = new StringBuilder(); 77 | for (int i = 0; i < header.BlockSize; i++) 78 | { 79 | if (i != 0 && i % 16 == 0) 80 | { 81 | sb.AppendLine(); 82 | } 83 | sb.AppendFormat("{0:X2} ", buf[i]); 84 | } 85 | sectorPreview.Text = sb.ToString(); 86 | } 87 | 88 | void RedoXts() 89 | { 90 | if (accessor == null) return; 91 | dataKey.Text = dataKey.Text.PadRight(32, '0'); 92 | tweakKey.Text = tweakKey.Text.PadRight(32, '0'); 93 | var data = dataKey.Text.FromHexCompact(); 94 | var tweak = tweakKey.Text.FromHexCompact(); 95 | uint start, sectorSize; 96 | uint.TryParse(xtsStartSector.Text, out start); 97 | uint.TryParse(xtsSectorSize.Text, out sectorSize); 98 | xtsReader = new XtsDecryptReader(accessor, data, tweak, start, sectorSize); 99 | UpdateHexView(); 100 | } 101 | 102 | private void button1_Click(object sender, EventArgs e) 103 | { 104 | ClosePfs(); 105 | using (var ofd = new OpenFileDialog() { Title = "Open a PFS image" }) 106 | { 107 | if (ofd.ShowDialog() == DialogResult.OK) 108 | { 109 | LoadPfs(ofd.FileName); 110 | } 111 | } 112 | } 113 | 114 | private void button2_Click(object sender, EventArgs e) 115 | { 116 | RedoXts(); 117 | reloadButton.Enabled = false; 118 | } 119 | 120 | private void invalidateSector(object sender, EventArgs e) 121 | { 122 | reloadButton.Enabled = true; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /LibOrbisPkg/LibOrbisPkg.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B053F491-FF0F-4CBB-B03B-520591BB0441} 8 | Library 9 | Properties 10 | LibOrbisPkg 11 | LibOrbisPkg 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 7.3 25 | true 26 | false 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 7.3 36 | true 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/FlatPathTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.IO; 5 | using System.Text; 6 | using LibOrbisPkg.Util; 7 | 8 | namespace LibOrbisPkg.PFS 9 | { 10 | /// 11 | /// Represents the flat_path_table file, which is a mapping of filename hash to inode number 12 | /// that the Orbis OS can use to speed up lookups. 13 | /// 14 | public class FlatPathTable 15 | { 16 | public static bool HasCollision(List nodes) 17 | { 18 | var hashSet = new HashSet(); 19 | foreach(var n in nodes) 20 | { 21 | var hash = HashFunction(n.FullPath()); 22 | if (hashSet.Contains(hash)) 23 | return true; 24 | hashSet.Add(hash); 25 | } 26 | return false; 27 | } 28 | public static Tuple Create(List nodes) 29 | { 30 | var hashMap = new SortedDictionary(); 31 | var nodeMap = new Dictionary>(); 32 | bool collision = false; 33 | foreach (var n in nodes) 34 | { 35 | var hash = HashFunction(n.FullPath()); 36 | if (hashMap.ContainsKey(hash)) 37 | { 38 | hashMap[hash] = 0x80000000; 39 | nodeMap[hash].Add(n); 40 | collision = true; 41 | } 42 | else 43 | { 44 | hashMap[hash] = n.ino.Number | (n is FSDir ? 0x20000000u : 0u); 45 | nodeMap[hash] = new List(); 46 | nodeMap[hash].Add(n); 47 | } 48 | } 49 | if(!collision) 50 | { 51 | return Tuple.Create(new FlatPathTable(hashMap), (CollisionResolver)null); 52 | } 53 | 54 | uint offset = 0; 55 | var colEnts = new List>(); 56 | foreach(var kv in hashMap.Where(kv => kv.Value == 0x80000000).ToList()) 57 | { 58 | hashMap[kv.Key] = 0x80000000 | offset; 59 | var entList = new List(); 60 | colEnts.Add(entList); 61 | foreach(var node in nodeMap[kv.Key]) 62 | { 63 | var d = new PfsDirent() 64 | { 65 | InodeNumber = node.ino.Number, 66 | Type = node is FSDir ? DirentType.Directory : DirentType.File, 67 | Name = node.FullPath(), 68 | }; 69 | entList.Add(d); 70 | offset += (uint)d.EntSize; 71 | } 72 | offset += 0x18; 73 | } 74 | return Tuple.Create(new FlatPathTable(hashMap), new CollisionResolver(colEnts)); 75 | } 76 | 77 | private SortedDictionary hashMap; 78 | 79 | public int Size => hashMap.Count * 8; 80 | 81 | /// 82 | /// Construct a flat_path_table out of the given filesystem nodes. 83 | /// 84 | /// 85 | public FlatPathTable(SortedDictionary hashMap) 86 | { 87 | this.hashMap = hashMap; 88 | } 89 | 90 | /// 91 | /// Write this file to the stream. 92 | /// 93 | /// 94 | public void WriteToStream(Stream s) 95 | { 96 | foreach (var hash in hashMap.Keys) 97 | { 98 | s.WriteUInt32LE(hash); 99 | s.WriteUInt32LE(hashMap[hash]); 100 | } 101 | } 102 | 103 | /// 104 | /// Hashes the given name for the table. 105 | /// 106 | /// 107 | /// 108 | private static uint HashFunction(string name) 109 | { 110 | uint hash = 0; 111 | foreach (var c in name) 112 | hash = char.ToUpper(c) + (31 * hash); 113 | return hash; 114 | } 115 | } 116 | 117 | public class CollisionResolver 118 | { 119 | public int Size { get; } 120 | 121 | public CollisionResolver(List> ents) 122 | { 123 | Entries = ents; 124 | var size = 0; 125 | foreach(var l in ents) 126 | { 127 | foreach(var e in l) 128 | { 129 | size += e.EntSize; 130 | } 131 | size += 0x18; 132 | } 133 | Size = size; 134 | } 135 | 136 | private List> Entries; 137 | public void WriteToStream(Stream s) 138 | { 139 | foreach(var d in Entries) 140 | { 141 | foreach(var e in d) 142 | { 143 | e.WriteToStream(s); 144 | } 145 | s.Position += 0x18; 146 | } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /LibOrbisPkg/Kimie/Counter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using LibOrbisPkg.PKG; 6 | using LibOrbisPkg.PFS; 7 | using System.IO; 8 | using System.IO.MemoryMappedFiles; 9 | using LibOrbisPkg.Util; 10 | using LibOrbisPkg.GP4; 11 | 12 | namespace LibOrbisPkg.Kimie 13 | { 14 | /// 15 | /// Contains functionality to create a GP4 from a PKG 16 | /// 17 | public class Counter 18 | { 19 | public static EntryId[] GeneratedEntries = new[] 20 | { 21 | EntryId.DIGESTS, 22 | EntryId.ENTRY_KEYS, 23 | EntryId.IMAGE_KEY, 24 | EntryId.GENERAL_DIGESTS, 25 | EntryId.METAS, 26 | EntryId.ENTRY_NAMES, 27 | EntryId.LICENSE_DAT, 28 | EntryId.LICENSE_INFO, 29 | EntryId.PSRESERVED_DAT, 30 | EntryId.PLAYGO_CHUNK_DAT, 31 | EntryId.PLAYGO_CHUNK_SHA, 32 | EntryId.PLAYGO_MANIFEST_XML, 33 | }; 34 | 35 | public int CountPkfFiles(MemoryMappedFile pkgFile, string passcode = null) 36 | { 37 | string FilePath = Path.Combine("C:\\Users\\Kimiegg\\Downloads\\PKG Editor Test\\", ".progress.txt"); 38 | 39 | 40 | int count = 0; 41 | int dirCount = 0; 42 | Pkg pkg; 43 | using (var f = pkgFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read)) 44 | pkg = new PkgReader(f).ReadPkg(); 45 | 46 | passcode = passcode ?? "00000000000000000000000000000000"; 47 | 48 | var project = Gp4Project.Create(ContentTypeToVolumeType(pkg.Header.content_type)); 49 | project.volume.Package.Passcode = passcode; 50 | project.volume.Package.ContentId = pkg.Header.content_id; 51 | project.volume.Package.AppType = project.volume.Type == VolumeType.pkg_ps4_app ? "full" : null; 52 | project.volume.Package.StorageType = project.volume.Type == VolumeType.pkg_ps4_app ? "digital50" : null; 53 | 54 | 55 | foreach (var meta in pkg.Metas.Metas) 56 | { 57 | if (GeneratedEntries.Contains(meta.id)) continue; 58 | if (!EntryNames.IdToName.ContainsKey(meta.id)) continue; 59 | count++; 60 | } 61 | byte[] ekpfs; 62 | if (pkg.CheckPasscode(passcode)) 63 | { 64 | ekpfs = Crypto.ComputeKeys(pkg.Header.content_id, passcode, 1); 65 | } 66 | else 67 | { 68 | ekpfs = pkg.GetEkpfs(); 69 | } 70 | using (var va = pkgFile.CreateViewAccessor((long)pkg.Header.pfs_image_offset, (long)pkg.Header.pfs_image_size, MemoryMappedFileAccess.Read)) 71 | { 72 | var outerPfs = new PfsReader(va, pkg.Header.pfs_flags, ekpfs); 73 | var inner = new PfsReader(new PFSCReader(outerPfs.GetFile("pfs_image.dat").GetView())); 74 | // Convert PFS image timestamp from UNIX time and save it in the project 75 | project.volume.TimeStamp = new DateTime(1970, 1, 1) 76 | .AddSeconds(inner.Header.InodeBlockSig.Time1_sec); 77 | var uroot = inner.GetURoot(); 78 | Dir dir = null; 79 | var projectDirs = new Queue(); 80 | var pfsDirs = new Queue(); 81 | pfsDirs.Enqueue(uroot); 82 | projectDirs.Enqueue(dir); 83 | while (pfsDirs.Count > 0) 84 | { 85 | dir = projectDirs.Dequeue(); 86 | foreach (var f in pfsDirs.Dequeue().children) 87 | { 88 | if (f is PfsReader.Dir d) 89 | { 90 | pfsDirs.Enqueue(d); 91 | projectDirs.Enqueue(project.AddDir(dir, d.name)); 92 | dirCount++; 93 | } 94 | else if (f is PfsReader.File file) 95 | { 96 | count++; 97 | } 98 | 99 | } 100 | } 101 | } 102 | return count; 103 | } 104 | private static VolumeType ContentTypeToVolumeType(ContentType t) 105 | { 106 | switch (t) 107 | { 108 | case ContentType.GD: 109 | return VolumeType.pkg_ps4_app; 110 | case ContentType.DP: 111 | return VolumeType.pkg_ps4_patch; 112 | case ContentType.AC: 113 | return VolumeType.pkg_ps4_ac_data; 114 | case ContentType.AL: 115 | return VolumeType.pkg_ps4_ac_nodata; 116 | default: 117 | return 0; 118 | } 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/PfsProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace LibOrbisPkg.PFS 7 | { 8 | /// 9 | /// A data structure representing everything configurable in a PFS image. 10 | /// Gets fed to a PfsBuilder. 11 | /// 12 | public class PfsProperties 13 | { 14 | public FSDir root; 15 | public long FileTime; 16 | public uint BlockSize; 17 | public uint MinBlocks = 0; 18 | public bool Encrypt; 19 | public bool Sign; 20 | public byte[] EKPFS; 21 | public byte[] Seed; 22 | 23 | /// 24 | /// Generates a PfsProperties object for the inner PFS image of a PKG with the given properties. 25 | /// 26 | /// 27 | /// 28 | public static PfsProperties MakeInnerPFSProps(PKG.PkgProperties props) 29 | { 30 | // Generate keystone for GP PKGs if it is not already there 31 | if(props.VolumeType == GP4.VolumeType.pkg_ps4_app && props.RootDir.GetFile("sce_sys/keystone") == null) 32 | { 33 | AddFile(props.RootDir, "sce_sys", "keystone", Util.Crypto.CreateKeystone(props.Passcode)); 34 | } 35 | var timestamp = props.TimeStamp.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; 36 | return new PfsProperties() 37 | { 38 | root = props.RootDir, 39 | BlockSize = 0x10000, 40 | // Hack: allocate a minimum of 5.5MiB of blocks for GP 41 | MinBlocks = props.VolumeType == GP4.VolumeType.pkg_ps4_app ? 0x55u : 0, 42 | Encrypt = false, 43 | Sign = false, 44 | FileTime = GetTimeStamp(props), 45 | }; 46 | } 47 | 48 | /// 49 | /// Generates a PfsProperties object for the outer PFS image of a PKG with the given properties. 50 | /// 51 | /// PKG properties to convert from 52 | /// Inner pfs image to use, presumably from MakeInnerPFSProps 53 | /// Encryption key for PFS 54 | /// Set to false to make a non-encrypted PFS 55 | /// 56 | public static PfsProperties MakeOuterPFSProps(PKG.PkgProperties props, PfsBuilder innerPFS, byte[] EKPFS, bool encrypt = true) 57 | { 58 | var root = new FSDir(); 59 | root.Files.Add(new FSFile(innerPFS) 60 | { 61 | Parent = root, 62 | }); 63 | return new PfsProperties() 64 | { 65 | root = root, 66 | BlockSize = 0x10000, 67 | Encrypt = encrypt, 68 | Sign = true, 69 | EKPFS = EKPFS, 70 | // This doesn't seem to really matter when verifying a PKG so use all zeroes for now 71 | Seed = new byte[16], 72 | FileTime = GetTimeStamp(props), 73 | }; 74 | } 75 | 76 | private static long GetTimeStamp(PKG.PkgProperties props) 77 | { 78 | // FIXME: This is incorrect when DST of current time and project time are different 79 | var timestamp = props.TimeStamp.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; 80 | return (long)timestamp; 81 | } 82 | 83 | static void AddFile(FSDir root, string path, string name, byte[] data) 84 | { 85 | var dir = FindDir(path, root); 86 | dir.Files.Add(new FSFile(s => s.Write(data, 0, data.Length), name, data.Length) { Parent = dir }); 87 | } 88 | 89 | /// 90 | /// Takes a directory and a root node, and recursively makes a filesystem tree. 91 | /// 92 | /// GP4 Project 93 | /// Directory of GP4 file 94 | /// Root directory of the image 95 | public static FSDir BuildFSTree(GP4.Gp4Project proj, string projDir) 96 | { 97 | void AddDirs(FSDir parent, List imgDir) 98 | { 99 | foreach (var d in imgDir) 100 | { 101 | FSDir dir; 102 | parent.Dirs.Add(dir = new FSDir { name = d.TargetName, Parent = parent }); 103 | AddDirs(dir, d.Children); 104 | } 105 | } 106 | var root = new FSDir(); 107 | AddDirs(root, proj.RootDir); 108 | 109 | foreach (var f in proj.files.Items) 110 | { 111 | var lastSlash = f.TargetPath.LastIndexOf('/') + 1; 112 | var name = f.TargetPath.Substring(lastSlash); 113 | var source = Path.Combine(projDir, f.OrigPath); 114 | var parent = lastSlash == 0 ? root : FindDir(f.TargetPath.Substring(0, lastSlash - 1), root); 115 | parent.Files.Add(new FSFile(source) 116 | { 117 | Parent = parent, 118 | name = name, 119 | }); 120 | } 121 | return root; 122 | } 123 | 124 | static FSDir FindDir(string name, FSDir root) 125 | { 126 | FSDir dir = root; 127 | var breadcrumbs = name.Split('/'); 128 | foreach (var crumb in breadcrumbs) 129 | { 130 | dir = dir.Dirs.Where(d => d.name == crumb).First(); 131 | } 132 | return dir; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /LibOrbisPkg/Rif/LicenseDat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using LibOrbisPkg.PKG; 5 | using LibOrbisPkg.Util; 6 | 7 | namespace LibOrbisPkg.Rif 8 | { 9 | public class LicenseDat : Entry 10 | { 11 | public LicenseDat() { } 12 | /// 13 | /// Constructs a signed debug license.dat 14 | /// 15 | public LicenseDat(string ContentId, ContentType contentType, byte[] EntitlementKey = null) 16 | { 17 | this.ContentId = ContentId; 18 | this.ContentType = contentType; 19 | this.SecretIv = new byte[16]; 20 | this.Secret = new byte[144]; 21 | 22 | var contentId = new byte[48]; 23 | Buffer.BlockCopy(Encoding.ASCII.GetBytes(ContentId), 0, contentId, 0, 36); 24 | var tmp = Crypto.Sha256(contentId); 25 | Buffer.BlockCopy(tmp, 0, SecretIv, 0, 16); 26 | Buffer.BlockCopy(tmp, 16, Secret, 0, 16); 27 | if(EntitlementKey != null && EntitlementKey.Length == 16) Buffer.BlockCopy(EntitlementKey, 0, Secret, 0x70, 16); 28 | if(ContentType == ContentType.GD) 29 | { 30 | SkuFlag = 3; // this is needed according to ShellCore 31 | } 32 | EncryptSecretWithDebugKey(); 33 | Sign(); 34 | } 35 | 36 | public short Version = 1; 37 | public short Unknown = -1; 38 | public ulong PsnAccountId = 0; 39 | public long StartTime = 1364222275L; 40 | public long EndTime = long.MaxValue; 41 | public string ContentId; 42 | public LicenseType LicenseType = LicenseType.Debug_0; 43 | public DrmType DrmType = DrmType.PS4; 44 | public ContentType ContentType = ContentType.AC; 45 | public short SkuFlag = 0; 46 | public int Flags = 0; 47 | public int Unk_5C = 0; 48 | public int Unk_60 = 0; 49 | public int Unk_64 = 1; 50 | public int Unk_Flag = 0; 51 | public byte[] DiscKey = new byte[32]; 52 | public byte[] SecretIv; 53 | public byte[] Secret; 54 | public byte[] Signature = new byte[256]; 55 | 56 | public override EntryId Id => EntryId.LICENSE_DAT; 57 | 58 | public override uint Length => 0x400; 59 | 60 | public override string Name => null; // Not saved with a name in the PKG 61 | 62 | public void DecryptSecretWithDebugKey() 63 | { 64 | Crypto.AesCbcCfb128Decrypt(Secret, Secret, Secret.Length, Keys.rif_debug_key, SecretIv); 65 | } 66 | 67 | public void EncryptSecretWithDebugKey() 68 | { 69 | Crypto.AesCbcCfb128Encrypt(Secret, Secret, Secret.Length, Keys.rif_debug_key, SecretIv); 70 | } 71 | 72 | public void Sign() 73 | { 74 | using (var ms = new MemoryStream()) 75 | { 76 | new LicenseDatWriter(ms).Write(this); 77 | var hash = Crypto.Sha256(ms, 0, 0x300); 78 | Signature = Crypto.RSA2048SignSha256(hash, RSAKeyset.DebugRifKeyset); 79 | } 80 | } 81 | 82 | public override void Write(Stream s) 83 | { 84 | new LicenseDatWriter(s).Write(this); 85 | } 86 | } 87 | 88 | public enum LicenseType 89 | { 90 | // Incomplete listing from psdevwiki 91 | KDS_1 = 0, 92 | KDS_2 = 1, 93 | KDS_3 = 2, 94 | Isolated_1 = 0x101, 95 | Isolated_2 = 0x302, 96 | Disc = 0x102, 97 | Debug_0 = 0x200, 98 | Debug_1 = 0x201, 99 | Debug_2 = 0x202, 100 | CEX = 0x303, 101 | Unknown = 0x304, 102 | DEX = 0x305 103 | } 104 | 105 | public class LicenseDatReader : ReaderBase 106 | { 107 | public LicenseDatReader(Stream stream) : base(true, stream) { } 108 | public LicenseDat Read() 109 | { 110 | if (Int() != 0x52494600) 111 | throw new Exception("License did not have expected RIF header"); 112 | var license = new LicenseDat() 113 | { 114 | Version = Short(), 115 | Unknown = Short(), 116 | PsnAccountId = ULong(), 117 | StartTime = Long(), 118 | EndTime = Long(), 119 | ContentId = Encoding.ASCII.GetString(ReadBytes(48)).Substring(0, 36), 120 | LicenseType = (LicenseType)Short(), 121 | DrmType = (DrmType)Short(), 122 | ContentType = (ContentType)Short(), 123 | SkuFlag = Short(), 124 | Flags = Int(), 125 | Unk_5C = Int(), 126 | Unk_60 = Int(), 127 | Unk_64 = Int(), 128 | Unk_Flag = Int(), 129 | }; 130 | s.Position += 468; 131 | license.DiscKey = ReadBytes(32); 132 | license.SecretIv = ReadBytes(16); 133 | license.Secret = ReadBytes(144); 134 | license.Signature = ReadBytes(256); 135 | return license; 136 | } 137 | } 138 | 139 | public class LicenseDatWriter : WriterBase 140 | { 141 | public LicenseDatWriter(Stream stream) : base(true, stream) { } 142 | 143 | public void Write(LicenseDat dat) 144 | { 145 | Write(0x52494600); // "RIF\0"; 146 | Write(dat.Version); 147 | Write(dat.Unknown); 148 | Write(dat.PsnAccountId); 149 | Write(dat.StartTime); 150 | Write(dat.EndTime); 151 | Write(Encoding.ASCII.GetBytes(dat.ContentId)); 152 | Write(new byte[12]); 153 | Write((short)dat.LicenseType); 154 | Write((short)dat.DrmType); 155 | Write((short)dat.ContentType); 156 | Write(dat.SkuFlag); 157 | Write(dat.Flags); 158 | Write(dat.Unk_5C); 159 | Write(dat.Unk_60); 160 | Write(dat.Unk_64); 161 | Write(dat.Unk_Flag); 162 | s.Position += 468; 163 | Write(dat.DiscKey); 164 | Write(dat.SecretIv); 165 | Write(dat.Secret); 166 | Write(dat.Signature); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /PkgEditor/ValidationDialog.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PkgEditor 2 | { 3 | partial class ValidationDialog 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.label1 = new System.Windows.Forms.Label(); 32 | this.fatalLabel = new System.Windows.Forms.Label(); 33 | this.ignoreButton = new System.Windows.Forms.Button(); 34 | this.cancelButton = new System.Windows.Forms.Button(); 35 | this.validateListBox1 = new PkgEditor.ValidateListBox(); 36 | this.SuspendLayout(); 37 | // 38 | // label1 39 | // 40 | this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 41 | | System.Windows.Forms.AnchorStyles.Right))); 42 | this.label1.Location = new System.Drawing.Point(0, 0); 43 | this.label1.Name = "label1"; 44 | this.label1.Padding = new System.Windows.Forms.Padding(0, 5, 0, 0); 45 | this.label1.Size = new System.Drawing.Size(594, 39); 46 | this.label1.TabIndex = 1; 47 | this.label1.Text = "The GP4 project has failed validation.\r\nThis means there could be problems when b" + 48 | "uilding or running the target PKG file."; 49 | this.label1.TextAlign = System.Drawing.ContentAlignment.TopCenter; 50 | // 51 | // fatalLabel 52 | // 53 | this.fatalLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 54 | | System.Windows.Forms.AnchorStyles.Right))); 55 | this.fatalLabel.Location = new System.Drawing.Point(0, 313); 56 | this.fatalLabel.Name = "fatalLabel"; 57 | this.fatalLabel.Size = new System.Drawing.Size(594, 24); 58 | this.fatalLabel.TabIndex = 2; 59 | this.fatalLabel.Text = "Please resolve all fatal errors before building the PKG."; 60 | this.fatalLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; 61 | // 62 | // ignoreButton 63 | // 64 | this.ignoreButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; 65 | this.ignoreButton.Location = new System.Drawing.Point(297, 287); 66 | this.ignoreButton.Name = "ignoreButton"; 67 | this.ignoreButton.Size = new System.Drawing.Size(174, 23); 68 | this.ignoreButton.TabIndex = 3; 69 | this.ignoreButton.Text = "&Ignore and Continue"; 70 | this.ignoreButton.UseVisualStyleBackColor = true; 71 | this.ignoreButton.Click += new System.EventHandler(this.ignoreButton_Click); 72 | // 73 | // cancelButton 74 | // 75 | this.cancelButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; 76 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 77 | this.cancelButton.Location = new System.Drawing.Point(117, 287); 78 | this.cancelButton.Name = "cancelButton"; 79 | this.cancelButton.Size = new System.Drawing.Size(174, 23); 80 | this.cancelButton.TabIndex = 4; 81 | this.cancelButton.Text = "&Cancel Build"; 82 | this.cancelButton.UseVisualStyleBackColor = true; 83 | // 84 | // validateListBox1 85 | // 86 | this.validateListBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 87 | | System.Windows.Forms.AnchorStyles.Left) 88 | | System.Windows.Forms.AnchorStyles.Right))); 89 | this.validateListBox1.FormattingEnabled = true; 90 | this.validateListBox1.IntegralHeight = false; 91 | this.validateListBox1.Location = new System.Drawing.Point(3, 42); 92 | this.validateListBox1.Name = "validateListBox1"; 93 | this.validateListBox1.ScrollAlwaysVisible = true; 94 | this.validateListBox1.Size = new System.Drawing.Size(591, 239); 95 | this.validateListBox1.TabIndex = 5; 96 | // 97 | // ValidationDialog 98 | // 99 | this.AcceptButton = this.ignoreButton; 100 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 101 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 102 | this.CancelButton = this.cancelButton; 103 | this.ClientSize = new System.Drawing.Size(594, 337); 104 | this.Controls.Add(this.validateListBox1); 105 | this.Controls.Add(this.cancelButton); 106 | this.Controls.Add(this.ignoreButton); 107 | this.Controls.Add(this.fatalLabel); 108 | this.Controls.Add(this.label1); 109 | this.Name = "ValidationDialog"; 110 | this.Text = "PKG Build Issues"; 111 | this.ResumeLayout(false); 112 | 113 | } 114 | 115 | #endregion 116 | private System.Windows.Forms.Label label1; 117 | private System.Windows.Forms.Label fatalLabel; 118 | private System.Windows.Forms.Button ignoreButton; 119 | private System.Windows.Forms.Button cancelButton; 120 | private ValidateListBox validateListBox1; 121 | } 122 | } -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/XtsDecryptReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using LibOrbisPkg.Util; 4 | 5 | namespace LibOrbisPkg.PFS 6 | { 7 | /// 8 | /// Provides XTS decryption on an IMemoryReader 9 | /// 10 | public class XtsDecryptReader : IMemoryReader 11 | { 12 | private byte[] dataKey; 13 | private byte[] tweakKey; 14 | /// 15 | /// Size of each XEX sector 16 | /// 17 | private uint sectorSize; 18 | /// 19 | /// Sector at and after which the encryption is active 20 | /// 21 | private uint cryptStartSector; 22 | private IMemoryReader reader; 23 | private static byte[] zeroes = new byte[16]; 24 | 25 | /// 26 | /// Creates an AES-XTS-128 stream. 27 | /// Reads will decrypt data. 28 | /// 29 | public XtsDecryptReader( 30 | IMemoryReader r, 31 | byte[] dataKey, 32 | byte[] tweakKey, uint startSector = 16, uint sectorSize = 0x1000) 33 | { 34 | cryptStartSector = startSector; 35 | this.sectorSize = sectorSize; 36 | this.dataKey = dataKey; 37 | this.tweakKey = tweakKey; 38 | reader = r; 39 | } 40 | 41 | public static unsafe void DecryptSector( 42 | Ctx context, 43 | byte[] sector, 44 | ulong sectorNum) 45 | { 46 | byte[] tweak = context.tweak, 47 | encryptedTweak = context.encryptedTweak, 48 | xor = context.xor; 49 | 50 | // Reset tweak to sector number 51 | Buffer.BlockCopy(BitConverter.GetBytes(sectorNum), 0, tweak, 0, 8); 52 | Buffer.BlockCopy(zeroes, 0, tweak, 8, 8); 53 | using (var tweakEncryptor = context.tweakCipher.CreateEncryptor()) 54 | using (var decryptor = context.cipher.CreateDecryptor()) 55 | { 56 | tweakEncryptor.TransformBlock(tweak, 0, 16, encryptedTweak, 0); 57 | for (int plaintextOffset = 0; plaintextOffset < sector.Length; plaintextOffset += 16) 58 | { 59 | fixed(byte* xor_ = xor) 60 | fixed(byte* encryptedTweak_ = encryptedTweak) 61 | fixed(byte* sector_ = §or[plaintextOffset]) 62 | { 63 | *((ulong*)xor_) = *((ulong*)sector_) ^ *((ulong*)encryptedTweak_); 64 | *((ulong*)xor_+1) = *((ulong*)sector_+1) ^ *((ulong*)encryptedTweak_+1); 65 | } 66 | decryptor.TransformBlock(xor, 0, 16, xor, 0); 67 | fixed (byte* xor_ = xor) 68 | fixed (byte* encryptedTweak_ = encryptedTweak) 69 | fixed (byte* sector_ = §or[plaintextOffset]) 70 | { 71 | *((ulong*)sector_) = *((ulong*)xor_) ^ *((ulong*)encryptedTweak_); 72 | *((ulong*)sector_ + 1) = *((ulong*)xor_ + 1) ^ *((ulong*)encryptedTweak_ + 1); 73 | } 74 | // GF-Multiply Tweak 75 | int feedback = 0; 76 | for (int k = 0; k < 16; k++) 77 | { 78 | byte tmp = encryptedTweak[k]; 79 | encryptedTweak[k] = (byte)(2 * encryptedTweak[k] | feedback); 80 | feedback = (tmp & 0x80) >> 7; 81 | } 82 | if (feedback != 0) 83 | encryptedTweak[0] ^= 0x87; 84 | } 85 | } 86 | } 87 | 88 | public class Ctx 89 | { 90 | public SymmetricAlgorithm cipher; 91 | public SymmetricAlgorithm tweakCipher; 92 | public byte[] tweak; 93 | public byte[] xor; 94 | public byte[] encryptedTweak; 95 | } 96 | 97 | /// 98 | /// Precondition: activeSector is set 99 | /// Postconditions: 100 | /// - sectorOffset is reset to 0 101 | /// - sectorBuf[] is filled with decrypted sector 102 | /// - position is updated 103 | /// 104 | private void ReadSectorBuffer(Ctx ctx, int currentSector, byte[] sectorBuf) 105 | { 106 | reader.Read(currentSector * sectorSize, sectorBuf, 0, (int)sectorSize); 107 | if (currentSector >= cryptStartSector) 108 | DecryptSector(ctx, sectorBuf, (ulong)currentSector); 109 | } 110 | 111 | private Ctx MakeCtx() => new Ctx 112 | { 113 | cipher = new AesManaged 114 | { 115 | Mode = CipherMode.ECB, 116 | KeySize = 128, 117 | Key = dataKey, 118 | Padding = PaddingMode.None, 119 | BlockSize = 128, 120 | }, 121 | tweakCipher = new AesManaged 122 | { 123 | Mode = CipherMode.ECB, 124 | KeySize = 128, 125 | Key = tweakKey, 126 | Padding = PaddingMode.None, 127 | BlockSize = 128, 128 | }, 129 | xor = new byte[16], 130 | encryptedTweak = new byte[16], 131 | tweak = new byte[16] 132 | }; 133 | 134 | public void Read(long position, byte[] buffer, int offset, int count) 135 | { 136 | var ctx = MakeCtx(); 137 | var sectorBuf = new byte[sectorSize]; 138 | var currentSector = (int)(position / sectorSize); 139 | var offsetIntoSector = (int)(position - (sectorSize * currentSector)); 140 | ReadSectorBuffer(ctx, currentSector, sectorBuf); 141 | int totalRead = 0; 142 | while (count > 0) 143 | { 144 | if (offsetIntoSector >= sectorSize) 145 | { 146 | currentSector++; 147 | ReadSectorBuffer(ctx, currentSector, sectorBuf); 148 | offsetIntoSector = 0; 149 | } 150 | int bufferedRead = Math.Min((int)sectorSize - offsetIntoSector, count); 151 | Buffer.BlockCopy(sectorBuf, offsetIntoSector, buffer, offset, bufferedRead); 152 | count -= bufferedRead; 153 | offset += bufferedRead; 154 | totalRead += bufferedRead; 155 | offsetIntoSector += bufferedRead; 156 | position += bufferedRead; 157 | } 158 | } 159 | 160 | public void Dispose() 161 | { 162 | // We don't own the IMemoryReader, so do nothing. 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /PkgEditor/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /PkgEditor/LogWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/PasscodeEntry.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/ValidationDialog.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/Views/CryptoDebug.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/Views/ObjectView.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/Views/PFSView.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PkgEditor/Views/FileView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using LibOrbisPkg.PFS; 11 | using PfsDir = LibOrbisPkg.PFS.PfsReader.Dir; 12 | using PfsFile = LibOrbisPkg.PFS.PfsReader.File; 13 | using PfsNode = LibOrbisPkg.PFS.PfsReader.Node; 14 | using System.IO; 15 | 16 | namespace PkgEditor.Views 17 | { 18 | public partial class FileView : UserControl 19 | { 20 | public FileView() 21 | { 22 | InitializeComponent(); 23 | } 24 | 25 | public void AddRoot(PfsReader p, string name) 26 | { 27 | var superroot = p.GetSuperRoot(); 28 | var root = new TreeNode(name) { Tag = superroot }; 29 | directoryTreeView.Nodes.Add(root); 30 | root.Nodes.Add("Loading", "Loading...", 0); 31 | ExpandNode(root); 32 | directoryTreeView.SelectedNode = root.Nodes[0]; 33 | } 34 | 35 | private void LoadDirectory(PfsDir directory) 36 | { 37 | currentFolderListView.Items.Clear(); 38 | foreach(var child in directory.children) 39 | { 40 | currentFolderListView.Items.Add( 41 | new ListViewItem(new[] { 42 | child.name, 43 | child is PfsDir ? "" : HumanReadableFileSize(child.compressed_size), 44 | child is PfsDir ? "" : HumanReadableFileSize(child.size), 45 | }, 46 | child is PfsDir ? 1 : 0) 47 | { 48 | Tag = child 49 | }); 50 | } 51 | } 52 | private void ExpandNode(TreeNode node) 53 | { 54 | if (node.Nodes.Count == 0 || node.Nodes[0].Tag == null) 55 | { 56 | node.Nodes.Clear(); 57 | foreach (var dir in (node.Tag as PfsDir).children) 58 | { 59 | if (dir is PfsDir d) 60 | { 61 | var newNode = new TreeNode(d.name) { Tag = d }; 62 | if (d.children.Any(x => x is PfsDir)) 63 | newNode.Nodes.Add("Loading", "Loading...", 0); 64 | node.Nodes.Add(newNode); 65 | } 66 | } 67 | } 68 | } 69 | private void DirectoryTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e) 70 | { 71 | ExpandNode(e.Node); 72 | } 73 | 74 | private void DirectoryTreeView_AfterSelect(object sender, TreeViewEventArgs e) 75 | { 76 | LoadDirectory(e.Node.Tag as PfsDir); 77 | } 78 | 79 | public static string HumanReadableFileSize(long size) 80 | { 81 | if (size > (1024 * 1024 * 1024)) 82 | { 83 | return (size / (double)(1024 * 1024 * 1024)).ToString("0.#") + " GiB"; 84 | } 85 | else if (size > (1024 * 1024)) 86 | { 87 | return (size / (double)(1024 * 1024)).ToString("0.#") + " MiB"; 88 | } 89 | else if (size > 1024) 90 | { 91 | return (size / 1024.0).ToString("0.#") + " KiB"; 92 | } 93 | else 94 | { 95 | return size.ToString() + " B"; 96 | } 97 | } 98 | 99 | private void Extract(PfsNode n, bool compressed = false) 100 | { 101 | var sfd = new SaveFileDialog() 102 | { 103 | FileName = n.name, 104 | }; 105 | if (sfd.ShowDialog() == DialogResult.OK) 106 | { 107 | if (n is PfsDir d) 108 | { 109 | Directory.CreateDirectory(sfd.FileName); 110 | SaveTo(d.children, sfd.FileName); 111 | } 112 | else if (n is PfsFile f) 113 | { 114 | f.Save(sfd.FileName, !compressed); 115 | } 116 | } 117 | } 118 | 119 | private void ExtractMultiple(IEnumerable n) 120 | { 121 | var sfd = new SaveFileDialog() { FileName = "Save this dummy file to the target directory"}; 122 | if (sfd.ShowDialog() == DialogResult.OK) 123 | { 124 | var directory = Path.GetDirectoryName(sfd.FileName); 125 | SaveTo(n, directory); 126 | } 127 | } 128 | 129 | // TODO: Parallelize this 130 | private void SaveTo(IEnumerable nodes, string path) 131 | { 132 | foreach(var n in nodes) 133 | { 134 | if(n is PfsFile f) 135 | { 136 | f.Save(Path.Combine(path, n.name)); 137 | } 138 | else if(n is PfsDir d) 139 | { 140 | var newPath = Path.Combine(path, d.name); 141 | Directory.CreateDirectory(newPath); 142 | SaveTo(d.children, newPath); 143 | } 144 | } 145 | } 146 | 147 | private void ExtractToolStripMenuItem_Click(object sender, EventArgs e) 148 | { 149 | if(directoryTreeView.Focused || (currentFolderListView.Focused && currentFolderListView.SelectedItems.Count == 0)) 150 | { 151 | if(directoryTreeView.SelectedNode?.Tag is PfsDir d) 152 | { 153 | Extract(d); 154 | } 155 | } 156 | else if(currentFolderListView.Focused) 157 | { 158 | if (currentFolderListView.SelectedItems.Count == 1) 159 | { 160 | Extract(currentFolderListView.SelectedItems[0].Tag as PfsNode); 161 | } 162 | else 163 | { 164 | var stuff = new List(); 165 | foreach (var i in currentFolderListView.SelectedItems) 166 | stuff.Add((i as ListViewItem)?.Tag as PfsNode); 167 | ExtractMultiple(stuff); 168 | } 169 | } 170 | } 171 | 172 | private void extractCompressedPFSCToolStripMenuItem_Click(object sender, EventArgs e) 173 | { 174 | if (currentFolderListView.Focused && currentFolderListView.SelectedItems.Count == 1 && currentFolderListView.SelectedItems[0].Tag is PfsNode n) 175 | { 176 | Extract(n, compressed: true); 177 | } 178 | } 179 | 180 | private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) 181 | { 182 | extractCompressedPFSCToolStripMenuItem.Enabled = false; 183 | if (currentFolderListView.Focused && currentFolderListView.SelectedItems.Count == 1 && currentFolderListView.SelectedItems[0].Tag is PfsNode n) 184 | { 185 | extractCompressedPFSCToolStripMenuItem.Enabled = n.compressed_size != n.size; 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/PFSCReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.IO.MemoryMappedFiles; 4 | using System.IO; 5 | using System.IO.Compression; 6 | using LibOrbisPkg.Util; 7 | 8 | namespace LibOrbisPkg.PFS 9 | { 10 | /// 11 | /// This wraps a Memory mapped file view of a PFSC file so that you can access it 12 | /// as though it were uncompressed. 13 | /// 14 | public class PFSCReader : IMemoryReader 15 | { 16 | public const int Magic = 0x43534650; 17 | private IMemoryAccessor _accessor; 18 | private PFSCHdr hdr; 19 | private long[] sectorMap; 20 | 21 | /// 22 | /// Creates a PFSCReader 23 | /// 24 | /// An IMemoryAccessor containing the PFSC file 25 | /// Thrown when the accessor is not a view of a PFSC file. 26 | public PFSCReader(IMemoryAccessor va) 27 | { 28 | _accessor = va; 29 | _accessor.Read(0, out hdr); 30 | if (hdr.Magic != Magic) 31 | throw new ArgumentException("Not a PFSC file: missing PFSC magic"); 32 | if (hdr.Unk4 != 0) 33 | throw new ArgumentException($"Not a PFSC file: unknown data at 0x4 (expected 0, got {hdr.Unk4})"); 34 | //if (hdr.Unk8 != 6) 35 | // throw new ArgumentException($"Not a PFSC file: unknown data at 0x8 (expected 6, got {hdr.Unk8})"); 36 | if (hdr.BlockSz != (int)hdr.BlockSz2) 37 | throw new ArgumentException("Not a PFSC file: block size mismatch"); 38 | 39 | var num_blocks = (int)(hdr.DataLength / hdr.BlockSz2); 40 | sectorMap = new long[num_blocks + 1]; 41 | _accessor.ReadArray(hdr.BlockOffsets, sectorMap, 0, num_blocks + 1); 42 | } 43 | 44 | /// 45 | /// Creates a PFSCReader 46 | /// 47 | /// A ViewAccessor containing the PFSC file 48 | /// Thrown when the accessor is not a view of a PFSC file. 49 | public PFSCReader(MemoryMappedViewAccessor va) : this(new MemoryMappedViewAccessor_(va)) 50 | { } 51 | 52 | public PFSCReader(IMemoryReader r) : this(new MemoryAccessor(r)) 53 | { } 54 | 55 | public int SectorSize => hdr.BlockSz; 56 | 57 | /// 58 | /// Reads the sector at the given index into the given byte array. 59 | /// 60 | /// sector index (multiply by SectorSize to get the byte offset) 61 | /// byte array where sector will be written 62 | public void ReadSector(int idx, byte[] output) 63 | { 64 | if (idx < 0 || idx > sectorMap.Length - 1) 65 | throw new ArgumentException("Invalid index", nameof(idx)); 66 | 67 | var sectorOffset = sectorMap[idx]; 68 | var sectorSize = sectorMap[idx + 1] - sectorOffset; 69 | 70 | if(sectorSize == hdr.BlockSz2) 71 | { 72 | // fast case: uncompressed sector 73 | _accessor.Read(sectorOffset, output, 0, hdr.BlockSz); 74 | } 75 | else if (sectorSize > hdr.BlockSz2) 76 | { 77 | Array.Clear(output, 0, hdr.BlockSz); 78 | } 79 | else 80 | { 81 | // slow case: compressed sector 82 | var sectorBuf = new byte[(int)sectorSize - 2]; 83 | _accessor.Read(sectorOffset + 2, sectorBuf, 0, (int)sectorSize - 2); 84 | using (var bufStream = new MemoryStream(sectorBuf)) 85 | using (var ds = new DeflateStream(bufStream, CompressionMode.Decompress)) 86 | { 87 | ds.Read(output, 0, hdr.BlockSz); 88 | } 89 | } 90 | } 91 | 92 | private void Read(long src, long count, Action Write) 93 | { 94 | if (src + count > hdr.DataLength) 95 | throw new ArgumentException("Attempt to read beyond end of file"); 96 | var sectorSize = hdr.BlockSz; 97 | var sectorBuffer = new byte[sectorSize]; 98 | var currentSector = (int)(src / sectorSize); 99 | var offsetIntoSector = (int)(src - (sectorSize * currentSector)); 100 | ReadSector(currentSector, sectorBuffer); 101 | while (count > 0 && src < hdr.DataLength) 102 | { 103 | if (offsetIntoSector >= sectorSize) 104 | { 105 | currentSector++; 106 | ReadSector(currentSector, sectorBuffer); 107 | offsetIntoSector = 0; 108 | } 109 | int bufferedRead = (int)Math.Min(sectorSize - offsetIntoSector, count); 110 | Write(sectorBuffer, offsetIntoSector, bufferedRead); 111 | count -= bufferedRead; 112 | offsetIntoSector += bufferedRead; 113 | src += bufferedRead; 114 | } 115 | } 116 | 117 | /// 118 | /// Read `count` bytes at location `src` into the writeable Stream `dest` 119 | /// 120 | /// Byte offset into PFSC 121 | /// Number of bytes to read 122 | /// Output stream 123 | public void Read(long src, long count, Stream dest) 124 | { 125 | Read(src, count, dest.Write); 126 | } 127 | 128 | /// 129 | /// Read `count` bytes at location `src` into the byte array at offset `offset` 130 | /// 131 | /// Byte offset into PFSC 132 | /// Output byte array 133 | /// Offset into byte array 134 | /// Number of bytes to read 135 | public void Read(long src, byte[] buffer, int offset, int count) 136 | { 137 | Read(src, count, (sectorBuffer, offsetIntoSector, bufferedRead) => 138 | { 139 | Buffer.BlockCopy(sectorBuffer, offsetIntoSector, buffer, offset, bufferedRead); 140 | offset += bufferedRead; 141 | }); 142 | } 143 | 144 | public void Dispose() 145 | { 146 | _accessor.Dispose(); 147 | } 148 | 149 | [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x30)] 150 | private struct PFSCHdr 151 | { 152 | public int Magic; 153 | public int Unk4; 154 | public int Unk8; 155 | public int BlockSz; 156 | public long BlockSz2; 157 | public long BlockOffsets; 158 | public ulong DataStart; 159 | public long DataLength; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /PkgEditor/Views/PkgView.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /PkgEditor/Views/SFOView.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122, 17 122 | 123 | 124 | 17, 17 125 | 126 | -------------------------------------------------------------------------------- /LibOrbisPkg/PKG/PkgReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LibOrbisPkg.Util; 3 | 4 | namespace LibOrbisPkg.PKG 5 | { 6 | public class PkgReader : Util.ReaderBase 7 | { 8 | public PkgReader(System.IO.Stream s) : base(true, s) 9 | { 10 | } 11 | 12 | public Pkg ReadPkg() 13 | { 14 | var header = ReadHeader(); 15 | s.Position = 0xFE0; 16 | var headerDigest = s.ReadBytes(32); 17 | var headerSignature = s.ReadBytes(256); 18 | s.Position = header.entry_table_offset; 19 | var metasEntry = new MetasEntry(); 20 | for (var i = 0; i < header.entry_count; i++) 21 | { 22 | metasEntry.Metas.Add(MetaEntry.Read(s)); 23 | } 24 | var pkg = new Pkg 25 | { 26 | Header = header, 27 | HeaderDigest = headerDigest, 28 | HeaderSignature = headerSignature, 29 | Metas = metasEntry, 30 | }; 31 | foreach (var entry in pkg.Metas.Metas) 32 | { 33 | switch (entry.id) 34 | { 35 | case EntryId.METAS: 36 | pkg.Metas.meta = entry; 37 | break; 38 | case EntryId.ENTRY_NAMES: 39 | pkg.EntryNames = NameTableEntry.Read(entry, s); 40 | break; 41 | case EntryId.PARAM_SFO: 42 | s.Position = entry.DataOffset; 43 | pkg.ParamSfo = new SfoEntry(SFO.ParamSfo.FromStream(s)); 44 | pkg.ParamSfo.meta = entry; 45 | break; 46 | case EntryId.ENTRY_KEYS: 47 | pkg.EntryKeys = KeysEntry.Read(entry, s); 48 | pkg.EntryKeys.meta = entry; 49 | break; 50 | case EntryId.IMAGE_KEY: 51 | s.Position = entry.DataOffset; 52 | pkg.ImageKey = new GenericEntry(EntryId.IMAGE_KEY) 53 | { 54 | FileData = s.ReadBytes((int)entry.DataSize), 55 | meta = entry 56 | }; 57 | break; 58 | case EntryId.GENERAL_DIGESTS: 59 | s.Position = entry.DataOffset; 60 | pkg.GeneralDigests = GeneralDigestsEntry.Read(s); 61 | pkg.GeneralDigests.meta = entry; 62 | break; 63 | case EntryId.DIGESTS: 64 | s.Position = entry.DataOffset; 65 | pkg.Digests = new GenericEntry(EntryId.DIGESTS) 66 | { 67 | FileData = s.ReadBytes((int)entry.DataSize), 68 | meta = entry, 69 | }; 70 | break; 71 | case EntryId.LICENSE_DAT: 72 | try 73 | { 74 | var licenseDatBytes = new byte[entry.DataSize]; 75 | s.Position = entry.DataOffset; 76 | s.Read(licenseDatBytes, 0, (int)entry.DataSize); 77 | using (var ms = new System.IO.MemoryStream(Entry.Decrypt(licenseDatBytes, pkg, entry))) 78 | { 79 | pkg.LicenseDat = new Rif.LicenseDatReader(ms).Read(); 80 | pkg.LicenseDat.meta = entry; 81 | } 82 | } 83 | catch (Exception) { } 84 | break; 85 | } 86 | } 87 | return pkg; 88 | } 89 | 90 | public Header ReadHeader() 91 | { 92 | var hdr = new Header(); 93 | s.Position = 0x00; 94 | hdr.CNTMagic = s.ReadASCIINullTerminated(4); 95 | if (hdr.CNTMagic != Pkg.MAGIC) 96 | throw new Exception("Invalid CNT header"); 97 | s.Position = 0x04; 98 | hdr.flags = (PKGFlags)UInt(); 99 | s.Position = 0x08; 100 | hdr.unk_0x08 = UInt(); 101 | s.Position = 0x0C; 102 | hdr.unk_0x0C = UInt(); /* 0xF */ 103 | s.Position = 0x10; 104 | hdr.entry_count = UInt(); 105 | s.Position = 0x14; 106 | hdr.sc_entry_count = UShort(); 107 | s.Position = 0x16; 108 | hdr.entry_count_2 = UShort(); /* same as entry_count */ 109 | s.Position = 0x18; 110 | hdr.entry_table_offset = UInt(); 111 | s.Position = 0x1C; 112 | hdr.main_ent_data_size = UInt(); 113 | s.Position = 0x20; 114 | hdr.body_offset = ULong(); 115 | s.Position = 0x28; 116 | hdr.body_size = ULong(); 117 | s.Position = 0x40; 118 | hdr.content_id = s.ReadASCIINullTerminated(Pkg.PKG_CONTENT_ID_SIZE); // Length = PKG_CONTENT_ID_SIZE 119 | s.Position = 0x70; 120 | hdr.drm_type = (DrmType)UInt(); 121 | s.Position = 0x74; 122 | hdr.content_type = (ContentType)UInt(); 123 | s.Position = 0x78; 124 | hdr.content_flags = (ContentFlags)UInt(); 125 | s.Position = 0x7C; 126 | hdr.promote_size = UInt(); 127 | s.Position = 0x80; 128 | hdr.version_date = UInt(); 129 | s.Position = 0x84; 130 | hdr.version_hash = UInt(); 131 | s.Position = 0x88; 132 | hdr.unk_0x88 = UInt(); /* for delta patches only? */ 133 | s.Position = 0x8C; 134 | hdr.unk_0x8C = UInt(); /* for delta patches only? */ 135 | s.Position = 0x90; 136 | hdr.unk_0x90 = UInt(); /* for delta patches only? */ 137 | s.Position = 0x94; 138 | hdr.unk_0x94 = UInt(); /* for delta patches only? */ 139 | s.Position = 0x98; 140 | hdr.iro_tag = (IROTag)UInt(); 141 | s.Position = 0x9C; 142 | hdr.ekc_version = UInt(); /* drm type version */ 143 | s.Position = 0x100; 144 | hdr.sc_entries1_hash = ReadBytes(Pkg.HASH_SIZE); 145 | s.Position = 0x120; 146 | hdr.sc_entries2_hash = ReadBytes(Pkg.HASH_SIZE); 147 | s.Position = 0x140; 148 | hdr.digest_table_hash = ReadBytes(Pkg.HASH_SIZE); 149 | s.Position = 0x160; 150 | hdr.body_digest = ReadBytes(Pkg.HASH_SIZE); 151 | 152 | // TODO: i think these fields are actually members of element of container array 153 | s.Position = 0x400; 154 | hdr.unk_0x400 = UInt(); 155 | s.Position = 0x404; 156 | hdr.pfs_image_count = UInt(); 157 | s.Position = 0x408; 158 | hdr.pfs_flags = ULong(); 159 | s.Position = 0x410; 160 | hdr.pfs_image_offset = ULong(); 161 | s.Position = 0x418; 162 | hdr.pfs_image_size = ULong(); 163 | s.Position = 0x420; 164 | hdr.mount_image_offset = ULong(); 165 | s.Position = 0x428; 166 | hdr.mount_image_size = ULong(); 167 | s.Position = 0x430; 168 | hdr.package_size = ULong(); 169 | s.Position = 0x438; 170 | hdr.pfs_signed_size = UInt(); 171 | s.Position = 0x43C; 172 | hdr.pfs_cache_size = UInt(); 173 | s.Position = 0x440; 174 | hdr.pfs_image_digest = ReadBytes(Pkg.HASH_SIZE); 175 | s.Position = 0x460; 176 | hdr.pfs_signed_digest = ReadBytes(Pkg.HASH_SIZE); 177 | s.Position = 0x480; 178 | hdr.pfs_split_size_nth_0 = ULong(); 179 | s.Position = 0x488; 180 | hdr.pfs_split_size_nth_1 = ULong(); 181 | return hdr; 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /LibOrbisPkg/PFS/FSTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace LibOrbisPkg.PFS 7 | { 8 | /// 9 | /// Base class for directories and files in a PFS image builder. 10 | /// 11 | public abstract class FSNode 12 | { 13 | /// 14 | /// The parent directory of this node. This should only be null for the root directory. 15 | /// 16 | public FSDir Parent = null; 17 | 18 | /// 19 | /// The name of this node. 20 | /// 21 | public string name; 22 | 23 | /// 24 | /// The inode describing this node. 25 | /// 26 | public inode ino; 27 | 28 | /// 29 | /// The actual size on disk of this node (i.e. the filesize or size of the dirent block) 30 | /// 31 | public virtual long Size { get; protected set; } 32 | 33 | /// 34 | /// The logical size of this file. Should only differ from Size in the case of a compressed (PFSC) file. 35 | /// 36 | public virtual long CompressedSize => Size; 37 | 38 | /// 39 | /// Get the full path of this file within the image. 40 | /// 41 | /// Optional suffix to append to the result 42 | /// 43 | public string FullPath(string suffix = "") 44 | { 45 | // Parent == null implies this is the root directory, which doesn't really have a name. 46 | if (Parent == null) return suffix; 47 | return Parent.FullPath("/" + name + suffix); 48 | } 49 | } 50 | 51 | /// 52 | /// Represents a directory in a PFS image builder. 53 | /// 54 | public class FSDir : FSNode 55 | { 56 | /// 57 | /// The directories in this directory. 58 | /// 59 | public List Dirs = new List(); 60 | /// 61 | /// The files in this directory. 62 | /// 63 | public List Files = new List(); 64 | 65 | /// 66 | /// The dirents describing the nodes in this directory. 67 | /// 68 | public List Dirents = new List(); 69 | 70 | public override long Size 71 | { 72 | get { return Dirents.Sum(d => d.EntSize); } 73 | } 74 | 75 | /// 76 | /// Gets all the dirs and files in this directory and subdirectories. 77 | /// 78 | /// all the dirs and files in this directory and subdirectories 79 | public List GetAllChildren() 80 | { 81 | var ret = new List(GetAllChildrenDirs()); 82 | ret.AddRange(GetAllChildrenFiles()); 83 | return ret; 84 | } 85 | 86 | /// 87 | /// Gets all the dirs in this directory and subdirectories. 88 | /// 89 | /// all the dirs in this directory and subdirectories 90 | public List GetAllChildrenDirs() 91 | { 92 | var ret = new List(Dirs); 93 | foreach (var dir in Dirs) 94 | foreach (var child in dir.GetAllChildrenDirs()) 95 | ret.Add(child); 96 | return ret; 97 | } 98 | 99 | /// 100 | /// Gets all the files in this directory and subdirectories. 101 | /// 102 | /// all the files in this directory and subdirectories 103 | public List GetAllChildrenFiles() 104 | { 105 | var ret = new List(Files); 106 | foreach (var dir in GetAllChildrenDirs()) 107 | foreach (var f in dir.Files) 108 | ret.Add(f); 109 | return ret; 110 | } 111 | 112 | /// 113 | /// Gets the file at the given path relative to this directory. 114 | /// 115 | /// For example, to get a file named "b" in a directory called "a" in this directory, 116 | /// you'd pass in "a/b". 117 | /// 118 | /// Relative path to the desired file 119 | /// The file, or null if it can't be found. 120 | public FSFile GetFile(string path) 121 | { 122 | var breadcrumbs = path.Split('/'); 123 | if(breadcrumbs.Length == 1) 124 | { 125 | return Files.Find(f => f.name == path); 126 | } 127 | var dir = Dirs.Find(d => d.name == breadcrumbs[0]); 128 | return dir?.GetFile(path.Substring(path.IndexOf('/') + 1)); 129 | } 130 | } 131 | 132 | /// 133 | /// Represents a File in a PFS image builder. 134 | /// 135 | public class FSFile : FSNode 136 | { 137 | /// 138 | /// Creates an FSFile from a real on-disk file. 139 | /// You need to set the name, inode, and parent. 140 | /// 141 | /// Real path to the file. 142 | public FSFile(string origFileName) 143 | { 144 | Write = s => { using (var f = File.OpenRead(origFileName)) f.CopyTo(s); }; 145 | Size = new FileInfo(origFileName).Length; 146 | _compressedSize = Size; 147 | } 148 | 149 | /// 150 | /// Creates an FSFile that represents the PFS image that will be created by the given PfsBuilder. 151 | /// Useful for creating the pfs_image.dat file within an outer PFS. 152 | /// You need to set the inode and parent. 153 | /// 154 | /// the PfsBuilder that this file represents 155 | public FSFile(PfsBuilder b) 156 | { 157 | var pfsc = new PFSCWriter(b.CalculatePfsSize()); 158 | Write = s => 159 | { 160 | pfsc.WritePFSCHeader(s); 161 | b.WriteImage(new Util.OffsetStream(s, s.Position)); 162 | }; 163 | _compressedSize = b.CalculatePfsSize(); 164 | Size = _compressedSize + pfsc.HeaderSize; 165 | name = "pfs_image.dat"; 166 | Compress = true; 167 | } 168 | 169 | /// 170 | /// A generic constructor for anything that can be written to a stream. 171 | /// Don't forget to set the inode and parent. 172 | /// 173 | /// A function that takes a Stream and writes this file's data to it. 174 | /// This file's name 175 | /// The total size in bytes that will be written by writer 176 | public FSFile(Action writer, string name, long size) 177 | { 178 | Write = writer; 179 | this.name = name; 180 | Size = size; 181 | _compressedSize = Size; 182 | } 183 | private long _compressedSize; 184 | public override long CompressedSize => _compressedSize; 185 | /// 186 | /// Call this to write the file to a stream. 187 | /// 188 | public readonly Action Write; 189 | /// 190 | /// Flag for PFSC encoded files 191 | /// 192 | public bool Compress = false; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /LibOrbisPkg/PlayGo/ChunkDat.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using LibOrbisPkg.PKG; 5 | using LibOrbisPkg.Util; 6 | 7 | namespace LibOrbisPkg.PlayGo 8 | { 9 | public class ChunkDat : Entry 10 | { 11 | public uint magic; 12 | public ushort version_major; 13 | public ushort version_minor; 14 | public ushort image_count; 15 | public ushort chunk_count; 16 | public ushort mchunk_count; 17 | public ushort scenario_count; 18 | public uint file_size; 19 | public ushort default_scenario_id; 20 | public ushort attrib; 21 | public uint sdk_ver; 22 | public ushort disc_count; 23 | public ushort layer_bmp; 24 | public byte[] reserved; 25 | public string content_id; 26 | 27 | public List ChunkAtrrs; 28 | public List ChunkMchunks; 29 | public List ChunkLabels; 30 | public List MchunkAttrs; 31 | public List InnerMChunkAttrs; 32 | public List ScenarioAttrs; 33 | public List ScenarioChunks; 34 | public List ScenarioLabels; 35 | 36 | public override EntryId Id => EntryId.PLAYGO_CHUNK_DAT; 37 | 38 | public override uint Length => file_size; 39 | 40 | public override string Name => "playgo-chunk.dat"; 41 | 42 | public static ChunkDat FromProject(string contentId) 43 | { 44 | var dat = new ChunkDat 45 | { 46 | magic = 0x6f676c70, // 'plgo' 47 | version_major = 0x0000, 48 | version_minor = 0x0000, 49 | image_count = 1, 50 | chunk_count = 1, 51 | mchunk_count = 1, 52 | scenario_count = 1, 53 | file_size = 416, 54 | default_scenario_id = 0, 55 | attrib = 1, 56 | sdk_ver = 0, 57 | disc_count = 0, 58 | layer_bmp = 0, 59 | reserved = new byte[32].Fill((byte)0xff), 60 | content_id = contentId, 61 | ChunkAtrrs = new List() 62 | { 63 | new ChunkAttr 64 | { 65 | flag = 0x80, 66 | image_disc_layer_no = 0, 67 | req_locus = 3, 68 | mchunk_count = 1, 69 | language_mask = 0xFFFFFFFFFFFFFFFFUL, 70 | mchunks_offset = 0, 71 | label_offset = 0 72 | } 73 | }, 74 | ChunkMchunks = new List { 0 }, 75 | ChunkLabels = new List { "Chunk #0" }, 76 | MchunkAttrs = new List 77 | { 78 | new MChunkAttr 79 | { 80 | offset = 0, 81 | size = 0, // must update this to outer pfs image size + pfs offset 82 | } 83 | }, 84 | InnerMChunkAttrs = new List 85 | { 86 | new MChunkAttr 87 | { 88 | offset = 0, 89 | size = 0, // must update this to inner pfs image size 90 | } 91 | }, 92 | ScenarioAttrs = new List 93 | { 94 | new ScenarioAttr 95 | { 96 | type = 1, 97 | initial_chunk_count = 1, 98 | chunk_count = 1, 99 | chunks_offset = 0, 100 | label_offset = 0, 101 | }, 102 | }, 103 | ScenarioChunks = new List { 0 }, 104 | ScenarioLabels = new List { "Scenario #0" }, 105 | }; 106 | return dat; 107 | } 108 | 109 | public override void Write(Stream s) 110 | { 111 | var start = s.Position; 112 | s.WriteUInt32LE(magic); 113 | s.WriteUInt16LE(version_major); 114 | s.WriteUInt16LE(version_minor); 115 | s.WriteUInt16LE(image_count); 116 | s.WriteUInt16LE(chunk_count); 117 | s.WriteUInt16LE(mchunk_count); 118 | s.WriteUInt16LE(scenario_count); 119 | s.WriteUInt32LE(file_size); 120 | s.WriteUInt16LE(default_scenario_id); 121 | s.WriteUInt16LE(attrib); 122 | s.WriteUInt32LE(sdk_ver); 123 | s.WriteUInt16LE(disc_count); 124 | s.WriteUInt16LE(layer_bmp); 125 | s.Write(reserved, 0, 32); 126 | s.Write(Encoding.ASCII.GetBytes(content_id), 0, 36); 127 | s.Position = start + 0xC0; 128 | s.WriteUInt32LE(256); 129 | s.WriteUInt32LE(32); 130 | s.WriteUInt32LE(288); 131 | s.WriteUInt32LE(2); 132 | s.WriteUInt32LE(304); 133 | s.WriteUInt32LE(9); 134 | s.WriteUInt32LE(320); 135 | s.WriteUInt32LE(16); 136 | s.WriteUInt32LE(352); 137 | s.WriteUInt32LE(32); 138 | s.WriteUInt32LE(384); 139 | s.WriteUInt32LE(2); 140 | s.WriteUInt32LE(400); 141 | s.WriteUInt32LE(12); 142 | s.WriteUInt32LE(336); 143 | s.WriteUInt32LE(16); 144 | s.Position = start + 0x100; 145 | foreach (var x in ChunkAtrrs) x.WriteTo(s); 146 | s.Position = start + 0x120; 147 | foreach (var x in ChunkMchunks) s.WriteUInt16LE(x); 148 | s.Position = start + 0x130; 149 | foreach (var x in ChunkLabels) s.Write(Encoding.ASCII.GetBytes(x), 0, x.Length); 150 | s.Position = start + 0x140; 151 | foreach (var x in MchunkAttrs) x.WriteTo(s); 152 | s.Position = start + 0x150; 153 | foreach (var x in InnerMChunkAttrs) x.WriteTo(s); 154 | s.Position = start + 0x160; 155 | foreach (var x in ScenarioAttrs) x.WriteTo(s); 156 | s.Position = start + 0x180; 157 | foreach (var x in ScenarioChunks) s.WriteUInt16LE(x); 158 | s.Position = start + 0x190; 159 | foreach (var x in ScenarioLabels) s.Write(Encoding.ASCII.GetBytes(x), 0, x.Length); 160 | } 161 | } 162 | 163 | public struct ChunkAttr 164 | { 165 | public byte flag; 166 | public byte image_disc_layer_no; 167 | public byte req_locus; 168 | public ushort mchunk_count; 169 | public ulong language_mask; 170 | public uint mchunks_offset; 171 | public uint label_offset; 172 | 173 | public void WriteTo(System.IO.Stream s) 174 | { 175 | s.WriteByte(flag); 176 | s.WriteByte(image_disc_layer_no); 177 | s.WriteByte(req_locus); 178 | s.Position += 0xB; 179 | s.WriteUInt16LE(mchunk_count); 180 | s.WriteUInt64LE(language_mask); 181 | s.WriteUInt32LE(mchunks_offset); 182 | s.WriteUInt32LE(label_offset); 183 | } 184 | } 185 | 186 | public class MChunkAttr 187 | { 188 | public ulong offset; 189 | public ulong size; 190 | public void WriteTo(System.IO.Stream s) 191 | { 192 | s.WriteUInt64LE(offset); 193 | s.WriteUInt64LE(size); 194 | } 195 | } 196 | public class ScenarioAttr 197 | { 198 | public byte type; 199 | public ushort initial_chunk_count; 200 | public ushort chunk_count; 201 | public uint chunks_offset; 202 | public uint label_offset; 203 | public void WriteTo(System.IO.Stream s) 204 | { 205 | s.WriteByte(type); 206 | s.Position += 0x13; 207 | s.WriteUInt16LE(initial_chunk_count); 208 | s.WriteUInt16LE(chunk_count); 209 | s.WriteUInt32LE(chunks_offset); 210 | s.WriteUInt32LE(label_offset); 211 | } 212 | } 213 | } 214 | 215 | -------------------------------------------------------------------------------- /LibOrbisPkg/GP4/Gp4Validator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using LibOrbisPkg.SFO; 9 | 10 | namespace LibOrbisPkg.GP4 11 | { 12 | public class ValidateResult 13 | { 14 | public enum ResultType 15 | { 16 | Warning, 17 | Fatal, 18 | } 19 | public readonly ResultType Type; 20 | public readonly string Message; 21 | internal ValidateResult(ResultType t, string m) 22 | { 23 | Type = t; 24 | Message = m; 25 | } 26 | public static ValidateResult Fatal(string message) 27 | { 28 | return new ValidateResult(ResultType.Fatal, message); 29 | } 30 | public static ValidateResult Warning(string messsage) 31 | { 32 | return new ValidateResult(ResultType.Warning, messsage); 33 | } 34 | } 35 | public class Gp4Validator 36 | { 37 | private static ValidateResult checkPasscode(Gp4Project proj, string dir) 38 | { 39 | if (proj.volume.Package.Passcode.Length != 32) 40 | { 41 | return ValidateResult.Fatal("Passcode must be 32 characters long."); 42 | } 43 | return null; 44 | } 45 | private static ValidateResult checkAllFilesExist (Gp4Project proj, string dir) 46 | { 47 | var missingFiles = proj.files.Items 48 | .Where(f => Path.Combine(dir, f.OrigPath) is string filePath && !File.Exists(filePath)) 49 | .Aggregate((string)null, (s, f) => s == null ? f.OrigPath : (s + ", " + f.OrigPath)); 50 | if (missingFiles != null) 51 | { 52 | return ValidateResult.Fatal("Could not find source file(s): " + missingFiles); 53 | } 54 | return null; 55 | } 56 | private static ValidateResult checkDuplicateFilenames(Gp4Project proj, string dir) 57 | { 58 | var dupeFiles = proj.files.Items 59 | .GroupBy(f => f.TargetPath) 60 | .Where(g => g.Count() > 1) 61 | .Select(g => g.Key) 62 | .Aggregate((string)null, (s, f) => s == null ? f : (s + ", " + f)); 63 | if (dupeFiles != null) 64 | { 65 | return ValidateResult.Fatal("PKG has duplicate filename(s): " + dupeFiles); 66 | } 67 | return null; 68 | } 69 | private static ValidateResult checkContentIdFormat(Gp4Project proj, string dir) 70 | { 71 | var pkgContentId = proj.volume.Package.ContentId; 72 | Regex contentIdReg = new Regex("^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_00-[A-Z0-9]{16}$"); 73 | if (contentIdReg.IsMatch(pkgContentId)) 74 | { 75 | return null; 76 | } 77 | return ValidateResult.Warning( 78 | "PKG Content ID is the wrong format. " + 79 | "Format should be XXYYYY-XXXXYYYYY_00-ZZZZZZZZZZZZZZZZ, where X is a letter, Y is a number, and Z is either."); 80 | } 81 | private static ValidateResult checkContentIdLength(Gp4Project proj, string dir) 82 | { 83 | if (proj.volume.Package.ContentId.Length != 36) 84 | { 85 | return ValidateResult.Fatal("PKG Content ID must be 36 characters long."); 86 | } 87 | return null; 88 | } 89 | private static ValidateResult checkPkgVolumeType(Gp4Project proj, string dir) 90 | { 91 | var pkgType = proj.volume.Type; 92 | switch (pkgType) 93 | { 94 | case VolumeType.pkg_ps4_app: 95 | break; 96 | case VolumeType.pkg_ps4_ac_data: 97 | break; 98 | case VolumeType.pkg_ps4_ac_nodata: 99 | break; 100 | default: 101 | return ValidateResult.Fatal( 102 | "Unsupported PKG volume type: " + pkgType); 103 | } 104 | return null; 105 | } 106 | private static List> commonChecks = new List>() 107 | { 108 | checkPasscode, 109 | checkAllFilesExist, 110 | checkDuplicateFilenames, 111 | checkContentIdLength, 112 | checkContentIdFormat, 113 | checkPkgVolumeType, 114 | }; 115 | private static ValidateResult checkContentIdMatchesSfo(Gp4Project proj, string dir, ParamSfo sfo) 116 | { 117 | var pkgContentId = proj.volume.Package.ContentId; 118 | var sfoContentId = sfo["CONTENT_ID"].ToString(); 119 | if (pkgContentId != sfoContentId) 120 | { 121 | return ValidateResult.Warning( 122 | $"PKG Content ID {pkgContentId} does not match CONTENT_ID {sfoContentId} in param.sfo."); 123 | } 124 | return null; 125 | } 126 | private static ValidateResult checkContentIdMatchesTitleIdSfo(Gp4Project proj, string dir, ParamSfo sfo) 127 | { 128 | var sfoContentId = sfo["CONTENT_ID"].ToString(); 129 | var sfoTitleId = sfo["TITLE_ID"].ToString(); 130 | if (sfoContentId.Substring(7).StartsWith(sfoTitleId)) 131 | { 132 | return null; 133 | } 134 | return ValidateResult.Warning( 135 | $"SFO TITLE_ID {sfoTitleId} does not match the CONTENT_ID {sfoContentId}."); 136 | } 137 | private static ValidateResult checkCategoryMatchesPkgType(Gp4Project proj, string dir, ParamSfo sfo) 138 | { 139 | var sfoCategory = sfo["CATEGORY"].ToString(); 140 | var pkgType = proj.volume.Type; 141 | bool ok = true; 142 | switch (pkgType) 143 | { 144 | case VolumeType.pkg_ps4_app: 145 | if (!sfoCategory.StartsWith("g")) 146 | ok = false; 147 | break; 148 | case VolumeType.pkg_ps4_ac_data: 149 | case VolumeType.pkg_ps4_ac_nodata: 150 | case VolumeType.pkg_ps4_theme: 151 | case VolumeType.pkg_ps4_sf_theme: 152 | if (sfoCategory != "ac") 153 | ok = false; 154 | break; 155 | } 156 | if (!ok) 157 | { 158 | return ValidateResult.Warning( 159 | $"SFO CATEGORY {sfoCategory} is not valid for PKG volume type {pkgType}."); 160 | } 161 | return null; 162 | } 163 | private static List> sfoChecks = new List>() 164 | { 165 | checkContentIdMatchesSfo, 166 | checkContentIdMatchesTitleIdSfo, 167 | checkCategoryMatchesPkgType 168 | }; 169 | 170 | public static List ValidateProject(Gp4Project proj, string projDir) 171 | { 172 | var ret = new List(); 173 | 174 | foreach(var check in commonChecks) 175 | { 176 | var result = check(proj, projDir); 177 | if (result != null) ret.Add(result); 178 | } 179 | // Checks with project and SFO file 180 | if (proj.files.Items.Where(f => f.TargetPath == "sce_sys/param.sfo").FirstOrDefault() is Gp4File sfoFile 181 | && Path.Combine(projDir, sfoFile.OrigPath) is string sfoPath 182 | && File.Exists(sfoPath)) 183 | { 184 | ParamSfo sfoObject = null; 185 | try 186 | { 187 | using (var f = File.OpenRead(sfoPath)) 188 | { 189 | sfoObject = ParamSfo.FromStream(f); 190 | } 191 | } 192 | catch (Exception e) 193 | { 194 | ret.Add(ValidateResult.Fatal("Could not load param.sfo file: " + e.Message)); 195 | } 196 | if (sfoObject != null) foreach (var check in sfoChecks) 197 | { 198 | var result = check(proj, projDir, sfoObject); 199 | if (result != null) ret.Add(result); 200 | } 201 | } 202 | else 203 | { 204 | ret.Add(ValidateResult.Fatal("Required file sce_sys/param.sfo is missing.")); 205 | } 206 | return ret; 207 | } 208 | } 209 | } 210 | --------------------------------------------------------------------------------