From d0071768ea6e87d7e6cd5b2de2f5250f0aaafa1e Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Fri, 18 Nov 2016 20:24:35 -0600 Subject: [PATCH] Added reference to SkyEditor.Core --- DotNet3dsToolkit/Converter.vb | 31 +- DotNet3dsToolkit/DotNet3dsToolkit.vbproj | 18 +- DotNet3dsToolkit/MetadataReader.vb | 1 + DotNet3dsToolkit/Misc/AsyncFor.vb | 199 --- DotNet3dsToolkit/Misc/GenericFile.vb | 780 ----------- DotNet3dsToolkit/Misc/GenericNDSRom.vb | 1239 +++++++++-------- DotNet3dsToolkit/Misc/IOProvider.vb | 137 -- .../Misc/LoadingStatusChangedEventArgs.vb | 10 - .../Misc/UnpackProgressEventArgs.vb | 7 - DotNet3dsToolkit/Misc/WindowsIOProvider.vb | 1 + DotNet3dsToolkit/packages.config | 6 + ToolkitConsole/ToolkitConsole.vbproj | 13 + ToolkitConsole/packages.config | 6 + ToolkitForm/Form1.vb | 7 +- ToolkitForm/ToolkitForm.vbproj | 13 + ToolkitForm/packages.config | 6 + 16 files changed, 721 insertions(+), 1753 deletions(-) delete mode 100644 DotNet3dsToolkit/Misc/AsyncFor.vb delete mode 100644 DotNet3dsToolkit/Misc/GenericFile.vb delete mode 100644 DotNet3dsToolkit/Misc/IOProvider.vb delete mode 100644 DotNet3dsToolkit/Misc/LoadingStatusChangedEventArgs.vb delete mode 100644 DotNet3dsToolkit/Misc/UnpackProgressEventArgs.vb create mode 100644 DotNet3dsToolkit/packages.config create mode 100644 ToolkitConsole/packages.config create mode 100644 ToolkitForm/packages.config diff --git a/DotNet3dsToolkit/Converter.vb b/DotNet3dsToolkit/Converter.vb index 1c89e05..99aa53f 100644 --- a/DotNet3dsToolkit/Converter.vb +++ b/DotNet3dsToolkit/Converter.vb @@ -1,9 +1,11 @@ Imports System.IO Imports System.Text.RegularExpressions Imports DotNet3dsToolkit.Misc +Imports SkyEditor.Core.Utilities Public Class Converter Implements IDisposable + Implements IReportProgress Public Event ConsoleOutputReceived(sender As Object, e As DataReceivedEventArgs) @@ -53,7 +55,8 @@ Public Class Converter End If End Sub - Public Event UnpackProgressed(sender As Object, e As UnpackProgressEventArgs) + Public Event UnpackProgressed(sender As Object, e As ProgressReportedEventArgs) Implements IReportProgress.ProgressChanged + Public Event Completed As IReportProgress.CompletedEventHandler Implements IReportProgress.Completed #Region "Tool Management" Private Property ToolDirectory As String @@ -63,6 +66,30 @@ Public Class Converter Private Property Path_ctrtool As String Private Property Path_ndstool As String + Public ReadOnly Property Progress As Single Implements IReportProgress.Progress + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Message As String Implements IReportProgress.Message + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property IsIndeterminate As Boolean Implements IReportProgress.IsIndeterminate + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property IsCompleted As Boolean Implements IReportProgress.IsCompleted + Get + Throw New NotImplementedException() + End Get + End Property + Private Sub ResetToolDirectory() ToolDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DotNet3DSToolkit-" & Guid.NewGuid.ToString) If Directory.Exists(ToolDirectory) Then @@ -501,7 +528,7 @@ Public Class Converter 'CopyNDSTool() 'Await RunProgram(Path_ndstool, String.Format("-v -x ""{0}"" -9 ""{1}/arm9.bin"" -7 ""{1}/arm7.bin"" -y9 ""{1}/y9.bin"" -y7 ""{1}/y7.bin"" -d ""{1}/data"" -y ""{1}/overlay"" -t ""{1}/banner.bin"" -h ""{1}/header.bin""", filename, outputDirectory)) - Dim reportProgress = Sub(sender As Object, e As UnpackProgressEventArgs) + Dim reportProgress = Sub(sender As Object, e As ProgressReportedEventArgs) RaiseEvent UnpackProgressed(Me, e) End Sub diff --git a/DotNet3dsToolkit/DotNet3dsToolkit.vbproj b/DotNet3dsToolkit/DotNet3dsToolkit.vbproj index ba36497..dd18098 100644 --- a/DotNet3dsToolkit/DotNet3dsToolkit.vbproj +++ b/DotNet3dsToolkit/DotNet3dsToolkit.vbproj @@ -43,6 +43,18 @@ On + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\SkyEditor.Core.4.0.7\lib\portable45-net45+win8+wpa81\SkyEditor.Core.dll + True + @@ -72,12 +84,7 @@ Language.resx - - - - - @@ -122,6 +129,7 @@ + diff --git a/DotNet3dsToolkit/MetadataReader.vb b/DotNet3dsToolkit/MetadataReader.vb index 4801386..7b1e2b8 100644 --- a/DotNet3dsToolkit/MetadataReader.vb +++ b/DotNet3dsToolkit/MetadataReader.vb @@ -1,6 +1,7 @@ Imports System.IO Imports System.Text Imports DotNet3dsToolkit.Misc +Imports SkyEditor.Core.IO ''' ''' Reads metadata from packed or unpacked ROMs. diff --git a/DotNet3dsToolkit/Misc/AsyncFor.vb b/DotNet3dsToolkit/Misc/AsyncFor.vb deleted file mode 100644 index 99624a7..0000000 --- a/DotNet3dsToolkit/Misc/AsyncFor.vb +++ /dev/null @@ -1,199 +0,0 @@ -Namespace Misc - ''' - ''' Runs a provided delegate function or sub repeatedly and asynchronously in the style of a For statement. - ''' - Public Class AsyncFor - - Public Delegate Sub ForItem(i As Integer) - Public Delegate Sub ForEachItem(Of T)(i As T) - Public Delegate Function ForEachItemAsync(Of T)(i As T) As Task - Public Delegate Function ForItemAsync(i As Integer) As Task - Public Event LoadingStatusChanged(sender As Object, e As LoadingStatusChangedEventArgs) - -#Region "Constructors" - Public Sub New() - BatchSize = Integer.MaxValue - RunningTasks = New List(Of Task) - End Sub - Public Sub New(ProgressMessage As String) - Me.New - End Sub -#End Region - -#Region "Properties" - - ''' - ''' Whether or not to run each task sequentially. - ''' - ''' - Public Property RunSynchronously As Boolean - - ''' - ''' The number of tasks to run at once. - ''' - ''' - Public Property BatchSize As Integer - - ''' - ''' The currently running tasks. - ''' - ''' - Private Property RunningTasks As List(Of Task) - - ''' - ''' The total number of tasks to run. - ''' - ''' - Private Property TotalTasks As Integer - - ''' - ''' The number of tasks that have been completed. - ''' - ''' - Private Property CompletedTasks As Integer - Get - Return _completedTasks - End Get - Set(value As Integer) - _completedTasks = value - RaiseEvent LoadingStatusChanged(Me, New LoadingStatusChangedEventArgs With {.Complete = (value = TotalTasks), - .Completed = value, - .Progress = If((TotalTasks > 0), (value / TotalTasks), 1), - .Total = TotalTasks}) - End Set - End Property - Dim _completedTasks As Integer - -#End Region - -#Region "Core Functions" - Public Async Function RunForEach(Of T)(DelegateFunction As ForEachItemAsync(Of T), Collection As IEnumerable(Of T)) As Task - 'Todo: throw exception if there's already tasks running - Dim taskItemQueue As New Queue(Of T) - For Each item In Collection - taskItemQueue.Enqueue(item) - Next - - TotalTasks = taskItemQueue.Count - - 'While there's either more tasks to start or while there's still tasks running - While (taskItemQueue.Count > 0 OrElse (taskItemQueue.Count = 0 AndAlso RunningTasks.Count > 0)) - If RunningTasks.Count < BatchSize AndAlso taskItemQueue.Count > 0 Then - 'We can run more tasks - - 'Get the next task item to run - Dim item = taskItemQueue.Dequeue 'The item in Collection to process - - 'Start the task - Dim tTask = Task.Run(Async Function() As Task - Await DelegateFunction(item) - System.Threading.Interlocked.Increment(CompletedTasks) - End Function) - - 'Either wait for it or move on - If RunSynchronously Then - Await tTask - Else - RunningTasks.Add(tTask) - End If - Else - If RunningTasks.Count > 0 Then - 'We can't start any more tasks, so we have to wait on one. - Await Task.WhenAny(RunningTasks) - - 'Remove completed tasks - For count = RunningTasks.Count - 1 To 0 Step -1 - If RunningTasks(count).GetAwaiter.IsCompleted Then - RunningTasks.RemoveAt(count) - End If - Next - Else - 'We're finished. Nothing else to do. - Exit While - End If - End If - End While - End Function - - Public Async Function RunFor(DelegateFunction As ForItemAsync, StartValue As Integer, EndValue As Integer, Optional StepCount As Integer = 1) As Task - 'Todo: throw exception if there's already tasks running - - If StepCount = 0 Then - Throw New ArgumentException(My.Resources.Language.ErrorAsyncForInfiniteLoop, NameOf(StepCount)) - End If - - 'Find how many tasks there are to run - 'The +1 here makes the behavior "For i = 0 to 10" have 11 loops - TotalTasks = Math.Ceiling((EndValue - StartValue + 1) / StepCount) - - If TotalTasks < 0 Then - 'Then in a normal For statement, the body would never be called - TotalTasks = 0 - CompletedTasks = 0 - Exit Function - End If - - Dim i As Integer = StartValue - - Dim tasksRemaining As Integer = TotalTasks 'The tasks that still need to be queued - - 'While there's either more tasks to start or while there's still tasks running - While (tasksRemaining > 0 OrElse (tasksRemaining = 0 AndAlso RunningTasks.Count > 0)) - If RunningTasks.Count < BatchSize AndAlso tasksRemaining > 0 Then - 'We can run more tasks - - Dim item = i 'To avoid async weirdness with having this in the below lambda - - 'Start the task - Dim tTask = Task.Run(Async Function() As Task - Await DelegateFunction(item) - System.Threading.Interlocked.Increment(CompletedTasks) - End Function) - - 'Increment for the next run - i += StepCount - - 'Either wait for it or move on - If RunSynchronously Then - Await tTask - Else - RunningTasks.Add(tTask) - End If - tasksRemaining -= 1 - Else - If tasksRemaining > 0 Then - 'We can't start any more tasks, so we have to wait on one. - Await Task.WhenAny(RunningTasks) - - 'Remove completed tasks - For count = RunningTasks.Count - 1 To 0 Step -1 - If RunningTasks(count).GetAwaiter.IsCompleted Then - RunningTasks.RemoveAt(count) - End If - Next - Else - 'We're finished. Nothing else to do. - Exit While - End If - End If - End While - End Function - - Public Async Function RunFor(DelegateSub As ForItem, StartValue As Integer, EndValue As Integer, Optional StepCount As Integer = 1) As Task - Await RunFor(Function(Count As Integer) As Task - DelegateSub(Count) - Return Task.FromResult(0) - End Function, StartValue, EndValue, StepCount) - End Function - - Public Async Function RunForEach(Of T)(DelegateSub As ForEachItem(Of T), Collection As IEnumerable(Of T)) As Task - Await RunForEach(Function(Item As T) As Task - DelegateSub(Item) - Return Task.FromResult(0) - End Function, Collection) - End Function -#End Region - - End Class -End Namespace - diff --git a/DotNet3dsToolkit/Misc/GenericFile.vb b/DotNet3dsToolkit/Misc/GenericFile.vb deleted file mode 100644 index 00bcc8e..0000000 --- a/DotNet3dsToolkit/Misc/GenericFile.vb +++ /dev/null @@ -1,780 +0,0 @@ -Imports System.IO -Imports System.Text - -Namespace Misc - Public Class GenericFile - Implements IDisposable - - Private _fileLock As New Object - - -#Region "Constructors" - ''' - ''' Creates a new instance of GenericFile for use with either GenericFile.OpenFile or GenericFile.CreateFile - ''' - Public Sub New() - IsReadOnly = False - EnableInMemoryLoad = False 'This is an opt-in setting - _enableShadowCopy = Nothing - End Sub - - ''' - ''' Creates a new instance of GenericFile for use with either GenericFile.OpenFile or GenericFile.CreateFile - ''' - Public Sub New(FileProvider As IOProvider) - Me.FileProvider = FileProvider - IsReadOnly = False - EnableInMemoryLoad = False 'This is an opt-in setting - _enableShadowCopy = Nothing - End Sub - - ''' - ''' Creates a new instance of GenericFile using the given data. - ''' - ''' - Public Sub New(FileProvider As IOProvider, RawData As Byte()) - Me.FileProvider = FileProvider - IsReadOnly = False - EnableInMemoryLoad = True - CreateFileInternal("", RawData, EnableInMemoryLoad) - End Sub - - ''' - ''' Creates a new instance of GenericFile from the given file. - ''' - ''' Full path of the file to load. - Public Sub New(FileProvider As IOProvider, Filename As String) - Me.FileProvider = FileProvider - Me.IsReadOnly = False - Me.EnableInMemoryLoad = False - OpenFileInternal(Filename) - End Sub - - ''' - ''' Creates a new instance of GenericFile from the given file. - ''' - ''' Full path of the file to load. - ''' Whether or not to allow altering the file. If True, an IOException will be thrown when attempting to alter the file. - Public Sub New(FileProvider As IOProvider, Filename As String, IsReadOnly As Boolean) - Me.FileProvider = FileProvider - Me.IsReadOnly = IsReadOnly - Me.EnableInMemoryLoad = False - OpenFileInternal(Filename) - End Sub - - ''' - ''' Creates a new instance of GenericFile from the given file. - ''' - ''' Full path of the file to load. - ''' Whether or not to allow altering the file. If True, an IOException will be thrown when attempting to alter the file, regardless of whether LoadToMemory is true. - ''' True to load the file into memory, False to use a FileStream. If loading the file into memory would leave the system with less than 500MB, a FileStream will be used instead. - Public Sub New(FileProvider As IOProvider, Filename As String, IsReadOnly As Boolean, LoadToMemory As Boolean) - Me.FileProvider = FileProvider - Me.IsReadOnly = IsReadOnly - Me.EnableInMemoryLoad = LoadToMemory - OpenFileInternal(Filename) - End Sub - -#End Region - -#Region "Properties" - - ''' - ''' Platform dependant abstraction layer for the file system. - ''' - ''' - Protected Property FileProvider As IOProvider - -#Region "File Loading/Management" - ''' - ''' Whether or not to allow altering the file. - ''' - ''' - Public Property IsReadOnly As Boolean - - ''' - ''' Determines whether or not the file will be loaded into memory completely, or located on disk. - ''' - ''' - Public Property EnableInMemoryLoad As Boolean - Get - Return (_enableInMemoryLoad.HasValue AndAlso _enableInMemoryLoad.Value) OrElse (Not _enableInMemoryLoad.HasValue AndAlso PhysicalFilename Is Nothing) - End Get - Set(value As Boolean) - _enableInMemoryLoad = value - End Set - End Property - Dim _enableInMemoryLoad As Boolean? - - ''' - ''' The logical location of the file. - ''' Null if the current file was created and has never been saved. - ''' - ''' - Public Property OriginalFilename As String 'Implements IOnDisk.Filename - - ''' - ''' The location of the file being accessed by the internal FileReader. - ''' If EnableShadowCopy is True, will be the location of a temporary file. If False, it is equal to OriginalFilename. - ''' Null if EnableInMemoryLoad is True. - ''' - ''' - Public Property PhysicalFilename As String - Get - Return _physicalFilename - End Get - Private Set(value As String) - _physicalFilename = value - End Set - End Property - Dim _physicalFilename As String - - ''' - ''' Name of the file. - ''' - ''' - Public Property Name As String 'Implements INamed.Name - Get - If _name Is Nothing Then - Return Path.GetFileName(OriginalFilename) - Else - Return _name - End If - End Get - Set(value As String) - _name = value - End Set - End Property - Dim _name As String - - ''' - ''' Whether or not to make a shadow copy of the file before loading it. - ''' - ''' - Public Property EnableShadowCopy As Boolean - Get - If _enableShadowCopy.HasValue Then - Return _enableShadowCopy - Else - Return Not IsReadOnly - End If - End Get - Set(value As Boolean) - _enableShadowCopy = value - End Set - End Property - Dim _enableShadowCopy As Boolean? - - Public ReadOnly Property IsThreadSafe As Boolean - Get - Return InMemoryFile IsNot Nothing - End Get - End Property -#End Region - -#Region "File Interaction" - ''' - ''' The raw data of the file, if EnableInMemoryLoad is True. - ''' - ''' - Private Property InMemoryFile As Byte() - - Protected ReadOnly Property FileReader As Stream - Get - If _fileReader Is Nothing Then - If IsReadOnly Then - _fileReader = FileProvider.OpenFileReadOnly(PhysicalFilename) - Else - _fileReader = FileProvider.OpenFile(PhysicalFilename) - End If - End If - - Return _fileReader - End Get - End Property - Dim _fileReader As Stream - - ''' - ''' Gets or sets the byte at the given index. - ''' - ''' Index of the byte. - ''' - Public Property RawData(Index As Long) As Byte - Get - If InMemoryFile IsNot Nothing Then - If InMemoryFile.Length > Index Then - Return InMemoryFile(Index) - Else - Throw New IndexOutOfRangeException("Index " & Index.ToString & " is out of range. Length of file: " & InMemoryFile.Length.ToString) - End If - Else - FileReader.Seek(Index, SeekOrigin.Begin) - Dim b = FileReader.ReadByte - If b > -1 AndAlso b < 256 Then - Return b - Else - Throw New IndexOutOfRangeException("Index " & Index.ToString & " is out of range. Length of file: " & FileReader.Length.ToString) - End If - End If - End Get - Set(value As Byte) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - If InMemoryFile IsNot Nothing Then - InMemoryFile(Index) = value - Else - FileReader.Seek(Index, SeekOrigin.Begin) - FileReader.WriteByte(value) - End If - End Set - End Property - Public Property RawData(Index As Long, Length As Long) As Byte() - Get - Dim output(Length - 1) As Byte - If InMemoryFile IsNot Nothing Then - For i = 0 To Length - 1 - output(i) = RawData(Index + i) - Next - Else - FileReader.Seek(Index, SeekOrigin.Begin) - FileReader.Read(output, 0, Length) - End If - Return output - End Get - Set(value As Byte()) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - If InMemoryFile IsNot Nothing Then - For i = 0 To Length - 1 - RawData(Index + i) = value(i) - Next - Else - FileReader.Seek(Index, SeekOrigin.Begin) - FileReader.Write(value, 0, Length) - End If - End Set - End Property - Public Property RawData() As Byte() - Get - If InMemoryFile IsNot Nothing Then - Return InMemoryFile - Else - Return RawData(0, Length) - End If - End Get - Set(value As Byte()) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - If InMemoryFile IsNot Nothing Then - InMemoryFile = value - Else - RawData(0, Length) = value - End If - End Set - End Property - - ''' - ''' Gets a 16 bit signed little endian int starting at the given index. - ''' - ''' - ''' - Public Property Int16(Index As Long) As Short - Get - Return BitConverter.ToInt16(RawData(Index, 2), 0) - End Get - Set(value As Short) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 2) = bytes - End Set - End Property - - ''' - ''' Gets a 16 bit unsigned little endian int starting at the given index. - ''' - ''' - ''' - Public Property UInt16(Index As Long) As UShort - Get - Return BitConverter.ToUInt16(RawData(Index, 2), 0) - End Get - Set(value As UShort) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 2) = bytes - End Set - End Property - - ''' - ''' Gets a 32 bit signed little endian int starting at the given index. - ''' - ''' - ''' - Public Property Int32(Index As Long) As Integer - Get - Return BitConverter.ToInt32(RawData(Index, 4), 0) - End Get - Set(value As Integer) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 4) = bytes - End Set - End Property - - ''' - ''' Gets a 32 bit unsingned little endian int starting at the given index. - ''' - ''' - ''' - Public Property UInt32(Index As Long) As UInteger - Get - Return BitConverter.ToUInt32(RawData(Index, 4), 0) - End Get - Set(value As UInteger) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 4) = bytes - End Set - End Property - - ''' - ''' Gets a 64 bit signed little endian int starting at the given index. - ''' - ''' - ''' - Public Property Int64(Index As Long) As Long - Get - Return BitConverter.ToInt64(RawData(Index, 8), 0) - End Get - Set(value As Long) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 8) = bytes - End Set - End Property - - ''' - ''' Gets a 64 bit unsingned little endian int starting at the given index. - ''' - ''' - ''' - Public Property UInt64(Index As Long) As ULong - Get - Return BitConverter.ToUInt64(RawData(Index, 8), 0) - End Get - Set(value As ULong) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - Dim bytes = BitConverter.GetBytes(value) - RawData(Index, 8) = bytes - End Set - End Property - - Public Property Length As Long - Get - If InMemoryFile IsNot Nothing Then - Return InMemoryFile.Length - Else - Return FileReader.Length - End If - End Get - Set(value As Long) - If IsReadOnly Then - Throw New IOException(My.Resources.Language.ErrorWrittenReadonly) - End If - If EnableInMemoryLoad Then - Array.Resize(InMemoryFile, value) - Else - FileReader.SetLength(value) - End If - End Set - End Property - - Public Property Position As ULong -#End Region - -#End Region - -#Region "Events" - - ''' - ''' Raised when the file has been saved to disk. - ''' - ''' - ''' - Public Event FileSaved(sender As Object, e As EventArgs) 'Implements ISavable.FileSaved - - ''' - ''' Raised when the file is being saved, but before any changes have been written to disk. - ''' - ''' - ''' - Public Event FileSaving(sender As Object, e As EventArgs) - - Protected Sub RaiseFileSaved(sender As Object, e As EventArgs) - RaiseEvent FileSaved(sender, e) - End Sub - -#End Region - -#Region "Functions" - -#Region "IO" - ''' - ''' Creates a new file with the given name. - ''' - ''' Name (not path) of the file. Include the extension if applicable. - Public Sub CreateFile(Name As String) 'Implements iCreatableFile.CreateFile - CreateFile(Name, {}) - End Sub - - ''' - ''' Creates a new file with the given contents. - ''' - ''' Contents of the new file. - Public Sub CreateFile(FileContents As Byte()) - CreateFile("", FileContents) - End Sub - - Public Overridable Sub CreateFile(Name As String, FileContents As Byte()) - CreateFileInternal(Name, FileContents, True) - End Sub - - Private Sub CreateFileInternal(Name As String, FileContents As Byte(), EnableInMemoryLoad As Boolean) - 'Load the file - Me.EnableInMemoryLoad = EnableInMemoryLoad - If EnableInMemoryLoad Then - 'Set the in-memory file to the given contents - Me.InMemoryFile = FileContents - Else - 'Save the file to a temporary filename - Me.PhysicalFilename = FileProvider.GetTempFilename - Me.OriginalFilename = Me.PhysicalFilename - FileProvider.WriteAllBytes(Me.PhysicalFilename, FileContents) - 'The file reader will be initialized when it's first needed - End If - - Me.Name = Name - End Sub - - ''' - ''' Opens a file from the given filename. If it does not exists, a blank one will be created. - ''' - ''' - Public Overridable Function OpenFile(Filename As String, Provider As IOProvider) As Task 'Implements IOpenableFile.OpenFile - Me.FileProvider = Provider - OpenFileInternal(Filename) - Return Task.FromResult(0) - End Function - - Private Sub OpenFileInternal(Filename As String) - Dim fileSize As Long = FileProvider.GetFileLength(Filename) - If (EnableInMemoryLoad AndAlso FileProvider.CanLoadFileInMemory(fileSize)) Then - 'Load the file into memory if it's enabled and it will fit into RAM, with 500MB left over, just in case. - Me.OriginalFilename = Filename - Me.PhysicalFilename = Filename - InMemoryFile = FileProvider.ReadAllBytes(Filename) - Else - 'The file will be read from disk. The only concern is whether or not we want to make a shadow copy. - If EnableShadowCopy Then - Me.OriginalFilename = Filename - Me.PhysicalFilename = FileProvider.GetTempFilename - If FileProvider.FileExists(Filename) Then - FileProvider.CopyFile(Filename, Me.PhysicalFilename) - Else - 'If the file doesn't exist, we'll create a file. - FileProvider.WriteAllBytes(Me.PhysicalFilename, {}) - End If - Else - Me.OriginalFilename = Filename - Me.PhysicalFilename = Filename - End If - 'The file stream will be initialized when it's needed. - End If - End Sub - - ''' - ''' Saves the file to the given destination. - ''' - ''' Full path of where the file should be saved to. - Public Overridable Sub Save(Destination As String, provider As IOProvider) ' Implements ISavableAs.Save - RaiseEvent FileSaving(Me, New EventArgs) - If InMemoryFile IsNot Nothing Then - provider.WriteAllBytes(Destination, InMemoryFile) - Else - FileReader.Seek(0, SeekOrigin.Begin) - FileReader.Flush() - If Not String.IsNullOrEmpty(Destination) Then - Using dest = provider.OpenFileWriteOnly(Destination) - FileReader.CopyTo(dest) - End Using - End If - End If - - If String.IsNullOrEmpty(OriginalFilename) Then - OriginalFilename = Destination - End If - RaiseEvent FileSaved(Me, New EventArgs) - End Sub - - ''' - ''' Saves the file to the Original Filename. - ''' Throws a NullReferernceException if the Original Filename is null. - ''' - Public Sub Save(provider As IOProvider) 'Implements ISavable.Save - If String.IsNullOrEmpty(Me.OriginalFilename) Then - Throw New NullReferenceException(My.Resources.Language.ErrorNoSaveFilename) - End If - Save(Me.OriginalFilename, provider) - End Sub -#End Region - -#Region "Data Interaction" - - ''' - ''' Reads all the data in the file. - ''' - ''' An array of byte containing the contents of the file. - ''' Not recommended for larger files. This function is thread-safe. - Public Async Function Read() As Task(Of Byte()) - If IsThreadSafe Then - Return RawData - Else - Return Await Task.Run(Function() As Byte() - SyncLock _fileLock - Return RawData - End SyncLock - End Function) - End If - End Function - - ''' - ''' Reads a byte from the file. - ''' - ''' Index from which to retrieve the byte. - ''' A byte equal to the byte at the given index in the file. - ''' This function is thread-safe. - Public Async Function Read(index As Long) As Task(Of Byte) - If IsThreadSafe Then - Return RawData(index) - Else - Return Await Task.Run(Function() As Byte - SyncLock _fileLock - Return RawData(index) - End SyncLock - End Function) - End If - End Function - - ''' - ''' Reads a range of bytes from the file. - ''' - ''' Index from which to retrieve the range. - ''' Length of the range. - ''' An array of byte containing the data in the requested range. - ''' This function is thread-safe. - Public Async Function Read(index As Long, length As Long) As Task(Of Byte()) - If IsThreadSafe Then - Return RawData(index, length) - Else - Return Await Task.Run(Function() As Byte() - SyncLock _fileLock - Return RawData(index, length) - End SyncLock - End Function) - End If - End Function - - ''' - ''' Copies data into the given stream. - ''' - ''' Stream to which to copy data. - ''' Index of the data to start reading from the . - ''' Number of bytes to copy into the destination stream. - ''' Thrown if is null. - ''' Currently, the data of size is buffered in memory, and will error if there is insufficient memory. - ''' - ''' To avoid threading issues, this function will synchronously block using SyncLock until the operation is complete. - Public Sub CopyTo(destination As Stream, index As Long, length As Long) - If IsThreadSafe Then - CopyToInternal(destination, index, length) - Else - SyncLock _fileLock - CopyToInternal(destination, index, length) - End SyncLock - End If - End Sub - - Private Sub CopyToInternal(destination As Stream, index As Long, length As Long) - If destination Is Nothing Then - Throw New ArgumentNullException(NameOf(destination)) - End If - - If InMemoryFile IsNot Nothing Then - destination.Write(InMemoryFile, index, length) - Else - Dim buffer(length) As Byte - FileReader.Seek(index, SeekOrigin.Begin) - FileReader.Read(buffer, 0, length) - destination.Write(buffer, 0, length) - End If - End Sub - - ''' - ''' Reads a UTF-16 string from the file. - ''' - ''' Location of the string in the file. - ''' Length, in characters, of the string. - ''' - Public Function ReadUnicodeString(Offset As Integer, Length As Integer) As String - Dim u = Text.Encoding.Unicode - Return u.GetString(RawData(Offset, Length * 2), 0, Length) - End Function - - ''' - ''' Reads a null-terminated UTF-16 string from the file. - ''' - ''' Location of the string in the file. - ''' - Public Function ReadUnicodeString(Offset As Integer) As String - 'Parse the null-terminated UTF-16 string - Dim s As New StringBuilder - Dim e = Text.Encoding.Unicode - Dim j As Integer = 0 - Dim cRaw As Byte() - Dim c As String - Do - cRaw = RawData(Offset + j * 2, 2) - c = e.GetString(cRaw, 0, 2) - - If Not c = vbNullChar Then - s.Append(c) - End If - - j += 1 - Loop Until c = vbNullChar - Return s.ToString - End Function - - ''' - ''' Reads a null-terminated string from the file using the given encoding. - ''' Currently only supports static 1 and 2 byte character formats. - ''' - ''' Location of the string in the file. - ''' Character encoding to use. Currently only supports static 1 and 2 byte character formats (like ASCII and UTF-16, but not UTF-8 or UTF-32). - ''' - Public Function ReadNullTerminatedString(Offset As Integer, e As Text.Encoding) As String - If e Is Text.Encoding.Unicode Then - Dim out As New Text.StringBuilder - Dim pos = Offset - Dim c As Char - Do - c = e.GetString(RawData(pos, 2), 0, 2) - If Not c = vbNullChar Then - out.Append(c) - End If - pos += 2 - Loop Until c = vbNullChar - Return out.ToString - Else - Dim out As New Text.StringBuilder - Dim pos = Offset - Dim c As Byte - Do - c = RawData(pos) - If Not c = 0 Then - out.Append(e.GetString({c}, 0, 1)) - End If - pos += 1 - Loop Until c = 0 - Return out.ToString - End If - End Function - - ''' - ''' Reads an unsigned 16 bit integer at the current position, then increments the current position by 2. - ''' - ''' - Public Function NextUInt16() As UInt16 - Dim out = UInt16(Position) - Position += 2 - Return out - End Function -#End Region - - ''' - ''' Default file extension for this kind of file. - ''' - ''' - Public Overridable Function GetDefaultExtension() As String 'Implements ISavableAs.GetDefaultExtension - Return Nothing - End Function - - Public Overridable Function GetSupportedExtensions() As IEnumerable(Of String) 'Implements ISavableAs.GetSupportedExtensions - Return Nothing - End Function - - Public Overrides Function ToString() As String - Return Me.Name - End Function - - - -#End Region - -#Region "IDisposable Support" - Private disposedValue As Boolean ' To detect redundant calls - - ' IDisposable - Protected Overridable Sub Dispose(disposing As Boolean) - If Not Me.disposedValue Then - If disposing Then - ' TODO: dispose managed state (managed objects). - If _fileReader IsNot Nothing Then - _fileReader.Dispose() - End If - If EnableShadowCopy Then - If FileProvider IsNot Nothing AndAlso FileProvider.FileExists(Me.PhysicalFilename) AndAlso Me.OriginalFilename <> Me.PhysicalFilename Then - FileProvider.DeleteFile(Me.PhysicalFilename) - End If - End If - End If - - ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. - ' TODO: set large fields to null. - End If - Me.disposedValue = True - End Sub - - ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources. - 'Protected Overrides Sub Finalize() - ' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. - ' Dispose(False) - ' MyBase.Finalize() - 'End Sub - - ' This code added by Visual Basic to correctly implement the disposable pattern. - Public Sub Dispose() Implements IDisposable.Dispose - ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. - Dispose(True) - GC.SuppressFinalize(Me) - End Sub - -#End Region - - End Class -End Namespace diff --git a/DotNet3dsToolkit/Misc/GenericNDSRom.vb b/DotNet3dsToolkit/Misc/GenericNDSRom.vb index ac9c8c6..47ea782 100644 --- a/DotNet3dsToolkit/Misc/GenericNDSRom.vb +++ b/DotNet3dsToolkit/Misc/GenericNDSRom.vb @@ -1,651 +1,670 @@ Imports System.Collections.Concurrent Imports System.Text +Imports SkyEditor.Core.IO +Imports SkyEditor.Core.Utilities -Namespace Misc - Public Class GenericNDSRom - Inherits GenericFile +Public Class GenericNDSRom + Inherits GenericFile + Implements IDetectableFileType + Implements IReportProgress - Public Overrides Function GetDefaultExtension() As String - Return "*.nds" - End Function + Public Overrides Function GetDefaultExtension() As String + Return "*.nds" + End Function #Region "Constructors" - Public Sub New() - MyBase.New() - Me.EnableInMemoryLoad = True - End Sub + Public Sub New() + MyBase.New() + Me.EnableInMemoryLoad = True + End Sub - Public Sub New(filename As String, isReadOnly As Boolean, enableInMemoryLoad As Boolean, provider As IOProvider) - MyBase.New(provider, filename, isReadOnly, enableInMemoryLoad) - End Sub + Public Sub New(filename As String, isReadOnly As Boolean, enableInMemoryLoad As Boolean, provider As IOProvider) + MyBase.New(provider, filename, isReadOnly, enableInMemoryLoad) + End Sub #End Region #Region "Events" - Public Event UnpackProgress(sender As Object, e As UnpackProgressEventArgs) + Public Event UnpackProgress(sender As Object, e As ProgressReportedEventArgs) Implements IReportProgress.ProgressChanged + Public Event Completed As IReportProgress.CompletedEventHandler Implements IReportProgress.Completed #End Region #Region "Properties" - 'Credit to http://nocash.emubase.de/gbatek.htm#dscartridgesencryptionfirmware (As of Jan 1 2014) for research - 'Later moved to http://problemkaputt.de/gbatek.htm#dscartridgeheader - Public Property GameTitle As String - Get - Dim e As New ASCIIEncoding - Return e.GetString(RawData(0, 12)).Trim - End Get - Set(value As String) - Dim e As New ASCIIEncoding - Dim buffer = e.GetBytes(value) - For count = 0 To 11 - If buffer.Length > count Then - RawData(count) = buffer(count) - Else - RawData(count) = 0 - End If - Next - End Set - End Property - Public Property GameCode As String - Get - Dim e As New ASCIIEncoding - Return e.GetString(RawData(12, 4)).Trim - End Get - Set(value As String) - Dim e As New ASCIIEncoding - Dim buffer = e.GetBytes(value) - For count = 0 To 3 - If buffer.Length > count Then - RawData(12 + count) = buffer(count) - Else - RawData(12 + count) = 0 - End If - Next - End Set - End Property - Private Property MakerCode As String - Get - Dim e As New ASCIIEncoding - Return e.GetString(RawData(16, 2)).Trim - End Get - Set(value As String) - Dim e As New ASCIIEncoding - Dim buffer = e.GetBytes(value) - For count = 0 To 1 - If buffer.Length > count Then - RawData(16 + count) = buffer(count) - Else - RawData(16 + count) = 0 - End If - Next - End Set - End Property - Private Property UnitCode As Byte - Get - Return RawData(&H12) - End Get - Set(value As Byte) - RawData(&H12) = value - End Set - End Property - Private Property EncryptionSeedSelect As Byte - Get - Return RawData(&H13) - End Get - Set(value As Byte) - RawData(&H13) = value - End Set - End Property - ''' - ''' Gets or sets the capacity of the cartridge. Cartridge size = 128KB * 2 ^ (DeviceCapacity) - ''' - ''' - ''' - ''' - Public Property DeviceCapacity As Byte - Get - Return RawData(&H13) - End Get - Set(value As Byte) - RawData(&H13) = value - End Set - End Property - 'Reserved: 9 bytes of 0 - Public Property RomVersion As Byte - Get - Return RawData(&H1E) - End Get - Set(value As Byte) - RawData(&H1E) = value - End Set - End Property - 'Autostart: bit 2 skips menu - Private Property Arm9RomOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H20, 4), 0) - End Get - Set(value As Integer) - RawData(&H20, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm9REntryAddress As Integer - Get - Return BitConverter.ToInt32(RawData(&H24, 4), 0) - End Get - Set(value As Integer) - RawData(&H24, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm9RamAddress As Integer - Get - Return BitConverter.ToInt32(RawData(&H28, 4), 0) - End Get - Set(value As Integer) - RawData(&H28, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm9Size As Integer - Get - Return BitConverter.ToInt32(RawData(&H2C, 4), 0) - End Get - Set(value As Integer) - RawData(&H2C, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm7RomOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H30, 4), 0) - End Get - Set(value As Integer) - RawData(&H30, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm7REntryAddress As Integer - Get - Return BitConverter.ToInt32(RawData(&H34, 4), 0) - End Get - Set(value As Integer) - RawData(&H34, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm7RamAddress As Integer - Get - Return BitConverter.ToInt32(RawData(&H38, 4), 0) - End Get - Set(value As Integer) - RawData(&H38, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property Arm7Size As Integer - Get - Return BitConverter.ToInt32(RawData(&H3C, 4), 0) - End Get - Set(value As Integer) - RawData(&H3C, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FilenameTableOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H40, 4), 0) - End Get - Set(value As Integer) - RawData(&H40, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FilenameTableSize As Integer - Get - Return BitConverter.ToInt32(RawData(&H44, 4), 0) - End Get - Set(value As Integer) - RawData(&H44, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileAllocationTableOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H48, 4), 0) - End Get - Set(value As Integer) - RawData(&H48, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileAllocationTableSize As Integer - Get - Return BitConverter.ToInt32(RawData(&H4C, 4), 0) - End Get - Set(value As Integer) - RawData(&H4C, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileArm9OverlayOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H50, 4), 0) - End Get - Set(value As Integer) - RawData(&H50, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileArm9OverlaySize As Integer - Get - Return BitConverter.ToInt32(RawData(&H54, 4), 0) - End Get - Set(value As Integer) - RawData(&H54, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileArm7OverlayOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H58, 4), 0) - End Get - Set(value As Integer) - RawData(&H58, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private Property FileArm7OverlaySize As Integer - Get - Return BitConverter.ToInt32(RawData(&H5C, 4), 0) - End Get - Set(value As Integer) - RawData(&H5C, 4) = BitConverter.GetBytes(value) - End Set - End Property - '060h 4 Port 40001A4h setting for normal commands (usually 00586000h) - '064h 4 Port 40001A4h setting for KEY1 commands (usually 001808F8h) - Private Property IconTitleOffset As Integer - Get - Return BitConverter.ToInt32(RawData(&H68, 4), 0) - End Get - Set(value As Integer) - RawData(&H68, 4) = BitConverter.GetBytes(value) - End Set - End Property - Private ReadOnly Property IconTitleLength As Integer - Get - Return &H840 - End Get - End Property - '06Ch 2 Secure Area Checksum, CRC-16 of [ [20h]..7FFFh] - '06Eh 2 Secure Area Loading Timeout (usually 051Eh) - '070h 4 ARM9 Auto Load List RAM Address (?) - '074h 4 ARM7 Auto Load List RAM Address (?) - '078h 8 Secure Area Disable (by encrypted "NmMdOnly") (usually zero) - '080h 4 Total Used ROM size (remaining/unused bytes usually FFh-padded) - '084h 4 ROM Header Size (4000h) - '088h 38h Reserved (zero filled) - '0C0h 9Ch Nintendo Logo (compressed bitmap, same as in GBA Headers) - '15Ch 2 Nintendo Logo Checksum, CRC-16 of [0C0h-15Bh], fixed CF56h - '15Eh 2 Header Checksum, CRC-16 of [000h-15Dh] - '160h 4 Debug rom_offset (0=none) (8000h and up) ;only if debug - '164h 4 Debug size (0=none) (max 3BFE00h) ;version with - '168h 4 Debug ram_address (0=none) (2400000h..27BFE00h) ;SIO and 8MB - '16Ch 4 Reserved (zero filled) (transferred, and stored, but not used) - '170h 90h Reserved (zero filled) (transferred, but not stored in RAM) -#End Region - -#Region "NitroRom Stuff" - -#Region "Private Classes" - Private Class OverlayTableEntry - Public Property OverlayID As Integer - Public Property RamAddress As Integer - Public Property RamSize As Integer - Public Property BssSize As Integer - Public Property StaticInitStart As Integer - Public Property StaticInitEnd As Integer - Public Property FileID As Integer - Public Sub New(RawData As Byte()) - OverlayID = BitConverter.ToInt32(RawData, 0) - RamAddress = BitConverter.ToInt32(RawData, 4) - RamSize = BitConverter.ToInt32(RawData, 8) - BssSize = BitConverter.ToInt32(RawData, &HC) - StaticInitStart = BitConverter.ToInt32(RawData, &H10) - StaticInitEnd = BitConverter.ToInt32(RawData, &H14) - FileID = BitConverter.ToInt32(RawData, &H18) - End Sub - End Class - - Private Class FileAllocationEntry - Public Property Offset As Integer - Public Property EndAddress As Integer - Public Sub New(Offset As Integer, EndAddress As Integer) - Me.Offset = Offset - Me.EndAddress = EndAddress - End Sub - End Class - - Private Class DirectoryMainTable - Public Property SubTableOffset As Integer - Public Property FirstSubTableFileID As UInt16 - ''' - ''' If this is the root directory, will contain the number of child directories. - ''' Otherwise, the ID of the parent directory. - ''' - ''' - Public Property ParentDir As UInt16 - Public Sub New(RawData As Byte()) - SubTableOffset = BitConverter.ToUInt32(RawData, 0) - FirstSubTableFileID = BitConverter.ToUInt16(RawData, 4) - ParentDir = BitConverter.ToUInt16(RawData, 6) - End Sub - End Class - - Private Class FNTSubTable - Public Property Length As Byte - Public Property Name As String - Public Property SubDirectoryID As UInt16 'Only for directories - Public Property ParentFileID As UInt16 - End Class - - Private Class FilenameTable - Public Property Name As String - Public Property FileIndex As Integer - Public ReadOnly Property IsDirectory As Boolean - Get - Return FileIndex < 0 - End Get - End Property - Public Property Children As List(Of FilenameTable) - Public Overrides Function ToString() As String - Return Name - End Function - Public Sub New() - FileIndex = -1 - Children = New List(Of FilenameTable) - End Sub - End Class -#End Region - - Private Function ParseArm9OverlayTable() As List(Of OverlayTableEntry) - Dim out As New List(Of OverlayTableEntry) - For count = FileArm9OverlayOffset To FileArm9OverlayOffset + FileArm9OverlaySize - 1 Step 32 - out.Add(New OverlayTableEntry(RawData(count, 32))) - Next - Return out - End Function - - Private Function ParseArm7OverlayTable() As List(Of OverlayTableEntry) - Dim out As New List(Of OverlayTableEntry) - For count = FileArm7OverlayOffset To FileArm7OverlayOffset + FileArm7OverlaySize - 1 Step 32 - out.Add(New OverlayTableEntry(RawData(count, 32))) - Next - Return out - End Function - - Private Function GetFAT() As List(Of FileAllocationEntry) - Dim out As New List(Of FileAllocationEntry) - For count = FileAllocationTableOffset To FileAllocationTableOffset + FileAllocationTableSize - 1 Step 8 - Dim entry As New FileAllocationEntry(BitConverter.ToUInt32(RawData(count, 4), 0), BitConverter.ToUInt32(RawData(count + 4, 4), 0)) - If Not entry.Offset = 0 Then - out.Add(entry) + 'Credit to http://nocash.emubase.de/gbatek.htm#dscartridgesencryptionfirmware (As of Jan 1 2014) for research + 'Later moved to http://problemkaputt.de/gbatek.htm#dscartridgeheader + Public Property GameTitle As String + Get + Dim e As New ASCIIEncoding + Return e.GetString(RawData(0, 12)).Trim + End Get + Set(value As String) + Dim e As New ASCIIEncoding + Dim buffer = e.GetBytes(value) + For count = 0 To 11 + If buffer.Length > count Then + RawData(count) = buffer(count) + Else + RawData(count) = 0 End If Next - Return out - End Function - - Private Function GetFNT() As FilenameTable - Dim root As New DirectoryMainTable(RawData(Me.FilenameTableOffset, 8)) - Dim rootDirectories As New List(Of DirectoryMainTable) - 'In the root entry, ParentDir means number of directories - For count = 8 To root.SubTableOffset - 1 Step 8 - rootDirectories.Add(New DirectoryMainTable(RawData(Me.FilenameTableOffset + count, 8))) - Next - 'Todo: read the relationship between directories and files - Dim out As New FilenameTable - out.Name = "data" - BuildFNT(out, root, rootDirectories) - Return out - End Function - Private Sub BuildFNT(ParentFNT As FilenameTable, root As DirectoryMainTable, Directories As List(Of DirectoryMainTable)) - For Each item In ReadFNTSubTable(root.SubTableOffset, root.FirstSubTableFileID) - Dim child As New FilenameTable With {.Name = item.Name} - ParentFNT.Children.Add(child) - If item.Length > 128 Then - BuildFNT(child, Directories((item.SubDirectoryID And &HFFF) - 1), Directories) + End Set + End Property + Public Property GameCode As String + Get + Dim e As New ASCIIEncoding + Return e.GetString(RawData(12, 4)).Trim + End Get + Set(value As String) + Dim e As New ASCIIEncoding + Dim buffer = e.GetBytes(value) + For count = 0 To 3 + If buffer.Length > count Then + RawData(12 + count) = buffer(count) Else - child.FileIndex = item.ParentFileID + RawData(12 + count) = 0 End If Next - End Sub - Private Function ReadFNTSubTable(RootSubTableOffset As Integer, ByVal ParentFileID As Integer) As List(Of FNTSubTable) - Dim subTables As New List(Of FNTSubTable) - Dim offset = RootSubTableOffset + Me.FilenameTableOffset - Dim length As Integer = RawData(offset) - While length > 0 - If length > 128 Then - 'Then it's a sub directory - 'Read the string - Dim buffer As Byte() = RawData(offset + 1, length - 128) - Dim s = (New ASCIIEncoding).GetString(buffer) - 'Read sub directory ID - Dim subDirID As UInt16 = Me.UInt16(offset + 1 + length - 128) - 'Add the result to the list - subTables.Add(New FNTSubTable With {.Length = length, .Name = s, .SubDirectoryID = subDirID}) - 'Increment the offset - offset += length - 128 + 1 + 2 - ElseIf length < 128 Then - 'Then it's a file - 'Read the string - Dim buffer As Byte() = RawData(offset + 1, length) - Dim s = (New ASCIIEncoding).GetString(buffer) - 'Add the result to the list - subTables.Add(New FNTSubTable With {.Length = length, .Name = s, .ParentFileID = ParentFileID}) - ParentFileID += 1 - 'Increment the offset - offset += length + 1 + End Set + End Property + Private Property MakerCode As String + Get + Dim e As New ASCIIEncoding + Return e.GetString(RawData(16, 2)).Trim + End Get + Set(value As String) + Dim e As New ASCIIEncoding + Dim buffer = e.GetBytes(value) + For count = 0 To 1 + If buffer.Length > count Then + RawData(16 + count) = buffer(count) Else - 'Reserved. I'm not sure what to do here. - Throw New NotSupportedException("Subtable length of 0x80 not supported.") + RawData(16 + count) = 0 End If + Next + End Set + End Property + Private Property UnitCode As Byte + Get + Return RawData(&H12) + End Get + Set(value As Byte) + RawData(&H12) = value + End Set + End Property + Private Property EncryptionSeedSelect As Byte + Get + Return RawData(&H13) + End Get + Set(value As Byte) + RawData(&H13) = value + End Set + End Property + ''' + ''' Gets or sets the capacity of the cartridge. Cartridge size = 128KB * 2 ^ (DeviceCapacity) + ''' + ''' + ''' + ''' + Public Property DeviceCapacity As Byte + Get + Return RawData(&H13) + End Get + Set(value As Byte) + RawData(&H13) = value + End Set + End Property + 'Reserved: 9 bytes of 0 + Public Property RomVersion As Byte + Get + Return RawData(&H1E) + End Get + Set(value As Byte) + RawData(&H1E) = value + End Set + End Property + 'Autostart: bit 2 skips menu + Private Property Arm9RomOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H20, 4), 0) + End Get + Set(value As Integer) + RawData(&H20, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm9REntryAddress As Integer + Get + Return BitConverter.ToInt32(RawData(&H24, 4), 0) + End Get + Set(value As Integer) + RawData(&H24, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm9RamAddress As Integer + Get + Return BitConverter.ToInt32(RawData(&H28, 4), 0) + End Get + Set(value As Integer) + RawData(&H28, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm9Size As Integer + Get + Return BitConverter.ToInt32(RawData(&H2C, 4), 0) + End Get + Set(value As Integer) + RawData(&H2C, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm7RomOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H30, 4), 0) + End Get + Set(value As Integer) + RawData(&H30, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm7REntryAddress As Integer + Get + Return BitConverter.ToInt32(RawData(&H34, 4), 0) + End Get + Set(value As Integer) + RawData(&H34, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm7RamAddress As Integer + Get + Return BitConverter.ToInt32(RawData(&H38, 4), 0) + End Get + Set(value As Integer) + RawData(&H38, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property Arm7Size As Integer + Get + Return BitConverter.ToInt32(RawData(&H3C, 4), 0) + End Get + Set(value As Integer) + RawData(&H3C, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FilenameTableOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H40, 4), 0) + End Get + Set(value As Integer) + RawData(&H40, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FilenameTableSize As Integer + Get + Return BitConverter.ToInt32(RawData(&H44, 4), 0) + End Get + Set(value As Integer) + RawData(&H44, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileAllocationTableOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H48, 4), 0) + End Get + Set(value As Integer) + RawData(&H48, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileAllocationTableSize As Integer + Get + Return BitConverter.ToInt32(RawData(&H4C, 4), 0) + End Get + Set(value As Integer) + RawData(&H4C, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileArm9OverlayOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H50, 4), 0) + End Get + Set(value As Integer) + RawData(&H50, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileArm9OverlaySize As Integer + Get + Return BitConverter.ToInt32(RawData(&H54, 4), 0) + End Get + Set(value As Integer) + RawData(&H54, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileArm7OverlayOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H58, 4), 0) + End Get + Set(value As Integer) + RawData(&H58, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private Property FileArm7OverlaySize As Integer + Get + Return BitConverter.ToInt32(RawData(&H5C, 4), 0) + End Get + Set(value As Integer) + RawData(&H5C, 4) = BitConverter.GetBytes(value) + End Set + End Property + '060h 4 Port 40001A4h setting for normal commands (usually 00586000h) + '064h 4 Port 40001A4h setting for KEY1 commands (usually 001808F8h) + Private Property IconTitleOffset As Integer + Get + Return BitConverter.ToInt32(RawData(&H68, 4), 0) + End Get + Set(value As Integer) + RawData(&H68, 4) = BitConverter.GetBytes(value) + End Set + End Property + Private ReadOnly Property IconTitleLength As Integer + Get + Return &H840 + End Get + End Property + '06Ch 2 Secure Area Checksum, CRC-16 of [ [20h]..7FFFh] + '06Eh 2 Secure Area Loading Timeout (usually 051Eh) + '070h 4 ARM9 Auto Load List RAM Address (?) + '074h 4 ARM7 Auto Load List RAM Address (?) + '078h 8 Secure Area Disable (by encrypted "NmMdOnly") (usually zero) + '080h 4 Total Used ROM size (remaining/unused bytes usually FFh-padded) + '084h 4 ROM Header Size (4000h) + '088h 38h Reserved (zero filled) + '0C0h 9Ch Nintendo Logo (compressed bitmap, same as in GBA Headers) + '15Ch 2 Nintendo Logo Checksum, CRC-16 of [0C0h-15Bh], fixed CF56h + '15Eh 2 Header Checksum, CRC-16 of [000h-15Dh] + '160h 4 Debug rom_offset (0=none) (8000h and up) ;only if debug + '164h 4 Debug size (0=none) (max 3BFE00h) ;version with + '168h 4 Debug ram_address (0=none) (2400000h..27BFE00h) ;SIO and 8MB + '16Ch 4 Reserved (zero filled) (transferred, and stored, but not used) + '170h 90h Reserved (zero filled) (transferred, but not stored in RAM) +#End Region - length = RawData(offset) - End While - Return subTables - End Function - ''' - ''' Extracts the files contained within the ROMs. - ''' Extractions either run synchronously or asynchrounously, depending on the value of IsThreadSafe. - ''' - ''' Directory to store the extracted files. - ''' - Public Async Function Unpack(TargetDir As String, Provider As IOProvider) As Task - Dim fat = GetFAT() - - 'Set up extraction dependencies - CurrentExtractProgress = 0 - CurrentExtractMax = fat.Count - ExtractionTasks = New ConcurrentBag(Of Task) - - 'Ensure directory exists - If Not Provider.DirectoryExists(TargetDir) Then - Provider.CreateDirectory(TargetDir) - End If - - 'Start extracting - '-Header - Dim headerTask = Task.Run(New Action(Sub() - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "header.bin"), RawData(0, &H200)) - End Sub)) - If Me.IsThreadSafe Then - ExtractionTasks.Add(headerTask) - Else - Await headerTask - End If - - '-Arm9 - Dim arm9Task = Task.Run(New Action(Sub() - Dim arm9buffer As New List(Of Byte) - arm9buffer.AddRange(RawData(Me.Arm9RomOffset, Me.Arm9Size)) - - 'Write an additional 0xC bytes if the next 4 equal: 21 06 C0 DE - Dim footer As Int32 = Int32(Me.Arm9RomOffset + Me.Arm9Size) - If footer = &HDEC00621 Then - arm9buffer.AddRange(RawData(Me.Arm9RomOffset + Me.Arm9Size, &HC)) - End If - - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "arm9.bin"), arm9buffer.ToArray) - End Sub)) - If Me.IsThreadSafe Then - ExtractionTasks.Add(arm9Task) - Else - Await arm9Task - End If - - '-Arm7 - Dim arm7Task = Task.Run(New Action(Sub() - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "arm7.bin"), RawData(Me.Arm7RomOffset, Me.Arm7Size)) - End Sub)) - If Me.IsThreadSafe Then - ExtractionTasks.Add(arm7Task) - Else - Await arm7Task - End If - - '-Arm9 overlay table (y9.bin) - Dim y9Task = Task.Run(New Action(Sub() - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "y9.bin"), RawData(Me.FileArm9OverlayOffset, Me.FileArm9OverlaySize)) - End Sub)) - - If Me.IsThreadSafe Then - ExtractionTasks.Add(y9Task) - Else - Await y9Task - End If - - '-Extract arm7 overlay table (y7.bin) - Dim y7Task = Task.Run(New Action(Sub() - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "y7.bin"), RawData(Me.FileArm7OverlayOffset, Me.FileArm7OverlaySize)) - End Sub)) - - If Me.IsThreadSafe Then - ExtractionTasks.Add(y7Task) - Else - Await y7Task - End If - '-Extract overlays - Dim overlay9 = ExtractOverlay(fat, ParseArm9OverlayTable, IO.Path.Combine(TargetDir, "overlay"), Provider) - - If Me.IsThreadSafe Then - ExtractionTasks.Add(overlay9) - Else - Await overlay9 - End If - - Dim overlay7 = ExtractOverlay(fat, ParseArm7OverlayTable, IO.Path.Combine(TargetDir, "overlay7"), Provider) - - If Me.IsThreadSafe Then - ExtractionTasks.Add(overlay7) - Else - Await overlay7 - End If - '-Extract icon (banner.bin) - Dim iconTask = Task.Run(Sub() - If IconTitleOffset > 0 Then '0 means none - Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "banner.bin"), RawData(IconTitleOffset, IconTitleLength)) - End If - End Sub) - - If Me.IsThreadSafe Then - ExtractionTasks.Add(iconTask) - Else - Await iconTask - End If +#Region "NitroRom Stuff" - '- Extract files - Dim filesExtraction = ExtractFiles(fat, GetFNT, TargetDir, Provider) - If Me.IsThreadSafe Then - ExtractionTasks.Add(filesExtraction) - Else - Await filesExtraction - End If +#Region "Private Classes" + Private Class OverlayTableEntry + Public Property OverlayID As Integer + Public Property RamAddress As Integer + Public Property RamSize As Integer + Public Property BssSize As Integer + Public Property StaticInitStart As Integer + Public Property StaticInitEnd As Integer + Public Property FileID As Integer + Public Sub New(RawData As Byte()) + OverlayID = BitConverter.ToInt32(RawData, 0) + RamAddress = BitConverter.ToInt32(RawData, 4) + RamSize = BitConverter.ToInt32(RawData, 8) + BssSize = BitConverter.ToInt32(RawData, &HC) + StaticInitStart = BitConverter.ToInt32(RawData, &H10) + StaticInitEnd = BitConverter.ToInt32(RawData, &H14) + FileID = BitConverter.ToInt32(RawData, &H18) + End Sub + End Class - 'Wait for everything to finish - Await Task.WhenAll(ExtractionTasks) - End Function + Private Class FileAllocationEntry + Public Property Offset As Integer + Public Property EndAddress As Integer + Public Sub New(Offset As Integer, EndAddress As Integer) + Me.Offset = Offset + Me.EndAddress = EndAddress + End Sub + End Class + Private Class DirectoryMainTable + Public Property SubTableOffset As Integer + Public Property FirstSubTableFileID As UInt16 ''' - ''' Extracts contained files if the file is thread safe, otherwise, extracts files one at a time. + ''' If this is the root directory, will contain the number of child directories. + ''' Otherwise, the ID of the parent directory. ''' - ''' - ''' - ''' ''' - Private Async Function ExtractFiles(FAT As List(Of FileAllocationEntry), Root As FilenameTable, TargetDir As String, Provider As IOProvider) As Task - Dim dest As String = IO.Path.Combine(TargetDir, Root.Name) - Dim f As New AsyncFor - f.RunSynchronously = Not Me.IsThreadSafe - f.BatchSize = Root.Children.Count - Await (f.RunForEach(Async Function(Item As FilenameTable) As Task - If Item.IsDirectory Then - Await ExtractFiles(FAT, Item, dest, Provider) - Else - Dim entry = FAT(Item.FileIndex) - Dim parentDir = IO.Path.GetDirectoryName(IO.Path.Combine(dest, Item.Name)) - If Not Provider.DirectoryExists(parentDir) Then - Provider.CreateDirectory(parentDir) - End If - Provider.WriteAllBytes(IO.Path.Combine(dest, Item.Name), RawData(entry.Offset, entry.EndAddress - entry.Offset)) - System.Threading.Interlocked.Increment(CurrentExtractProgress) - End If - End Function, Root.Children)) - End Function + Public Property ParentDir As UInt16 + Public Sub New(RawData As Byte()) + SubTableOffset = BitConverter.ToUInt32(RawData, 0) + FirstSubTableFileID = BitConverter.ToUInt16(RawData, 4) + ParentDir = BitConverter.ToUInt16(RawData, 6) + End Sub + End Class - Private Async Function ExtractOverlay(FAT As List(Of FileAllocationEntry), OverlayTable As List(Of OverlayTableEntry), TargetDir As String, Provider As IOProvider) As Task - If OverlayTable.Count > 0 AndAlso Not Provider.DirectoryExists(TargetDir) Then - Provider.CreateDirectory(TargetDir) - End If - Dim f As New AsyncFor - f.RunSynchronously = Not Me.IsThreadSafe - f.BatchSize = OverlayTable.Count - Await f.RunForEach(Sub(Item As OverlayTableEntry) - Dim dest = IO.Path.Combine(TargetDir, "overlay_" & Item.OverlayID.ToString.PadLeft(4, "0"c) & ".bin") - Dim entry = FAT(Item.FileID) - Provider.WriteAllBytes(dest, RawData(entry.Offset, entry.EndAddress - entry.Offset)) - End Sub, OverlayTable) - End Function + Private Class FNTSubTable + Public Property Length As Byte + Public Property Name As String + Public Property SubDirectoryID As UInt16 'Only for directories + Public Property ParentFileID As UInt16 + End Class - ''' - ''' Gets or sets the total number of files extracted in the current unpacking process. - ''' - ''' - Private Property CurrentExtractProgress As Integer + Private Class FilenameTable + Public Property Name As String + Public Property FileIndex As Integer + Public ReadOnly Property IsDirectory As Boolean Get - Return _extractProgress + Return FileIndex < 0 End Get - Set(value As Integer) - _extractProgress = value - RaiseEvent UnpackProgress(Me, New UnpackProgressEventArgs With {.FilesExtracted = value, .TotalFiles = CurrentExtractMax, .Progress = GetExtractionProgress()}) - End Set End Property - Dim _extractProgress As Integer - - ''' - ''' Gets or sets the total number of files in the current unpacking process. - ''' - ''' - Private Property CurrentExtractMax As Integer - - ''' - ''' Currently running tasks that are part of the unpacking process. - ''' - ''' - Private Property ExtractionTasks As ConcurrentBag(Of Task) + Public Property Children As List(Of FilenameTable) + Public Overrides Function ToString() As String + Return Name + End Function + Public Sub New() + FileIndex = -1 + Children = New List(Of FilenameTable) + End Sub + End Class +#End Region - ''' - ''' Gets the progress of the current unpacking process. - ''' - ''' - Public Function GetExtractionProgress() As Single - If CurrentExtractMax = 0 Then - Return 0 + Private Function ParseArm9OverlayTable() As List(Of OverlayTableEntry) + Dim out As New List(Of OverlayTableEntry) + For count = FileArm9OverlayOffset To FileArm9OverlayOffset + FileArm9OverlaySize - 1 Step 32 + out.Add(New OverlayTableEntry(RawData(count, 32))) + Next + Return out + End Function + + Private Function ParseArm7OverlayTable() As List(Of OverlayTableEntry) + Dim out As New List(Of OverlayTableEntry) + For count = FileArm7OverlayOffset To FileArm7OverlayOffset + FileArm7OverlaySize - 1 Step 32 + out.Add(New OverlayTableEntry(RawData(count, 32))) + Next + Return out + End Function + + Private Function GetFAT() As List(Of FileAllocationEntry) + Dim out As New List(Of FileAllocationEntry) + For count = FileAllocationTableOffset To FileAllocationTableOffset + FileAllocationTableSize - 1 Step 8 + Dim entry As New FileAllocationEntry(BitConverter.ToUInt32(RawData(count, 4), 0), BitConverter.ToUInt32(RawData(count + 4, 4), 0)) + If Not entry.Offset = 0 Then + out.Add(entry) + End If + Next + Return out + End Function + + Private Function GetFNT() As FilenameTable + Dim root As New DirectoryMainTable(RawData(Me.FilenameTableOffset, 8)) + Dim rootDirectories As New List(Of DirectoryMainTable) + 'In the root entry, ParentDir means number of directories + For count = 8 To root.SubTableOffset - 1 Step 8 + rootDirectories.Add(New DirectoryMainTable(RawData(Me.FilenameTableOffset + count, 8))) + Next + 'Todo: read the relationship between directories and files + Dim out As New FilenameTable + out.Name = "data" + BuildFNT(out, root, rootDirectories) + Return out + End Function + Private Sub BuildFNT(ParentFNT As FilenameTable, root As DirectoryMainTable, Directories As List(Of DirectoryMainTable)) + For Each item In ReadFNTSubTable(root.SubTableOffset, root.FirstSubTableFileID) + Dim child As New FilenameTable With {.Name = item.Name} + ParentFNT.Children.Add(child) + If item.Length > 128 Then + BuildFNT(child, Directories((item.SubDirectoryID And &HFFF) - 1), Directories) Else - Return CurrentExtractProgress / CurrentExtractMax + child.FileIndex = item.ParentFileID End If - End Function + Next + End Sub + Private Function ReadFNTSubTable(RootSubTableOffset As Integer, ByVal ParentFileID As Integer) As List(Of FNTSubTable) + Dim subTables As New List(Of FNTSubTable) + Dim offset = RootSubTableOffset + Me.FilenameTableOffset + Dim length As Integer = RawData(offset) + While length > 0 + If length > 128 Then + 'Then it's a sub directory + 'Read the string + Dim buffer As Byte() = RawData(offset + 1, length - 128) + Dim s = (New ASCIIEncoding).GetString(buffer) + 'Read sub directory ID + Dim subDirID As UInt16 = Me.UInt16(offset + 1 + length - 128) + 'Add the result to the list + subTables.Add(New FNTSubTable With {.Length = length, .Name = s, .SubDirectoryID = subDirID}) + 'Increment the offset + offset += length - 128 + 1 + 2 + ElseIf length < 128 Then + 'Then it's a file + 'Read the string + Dim buffer As Byte() = RawData(offset + 1, length) + Dim s = (New ASCIIEncoding).GetString(buffer) + 'Add the result to the list + subTables.Add(New FNTSubTable With {.Length = length, .Name = s, .ParentFileID = ParentFileID}) + ParentFileID += 1 + 'Increment the offset + offset += length + 1 + Else + 'Reserved. I'm not sure what to do here. + Throw New NotSupportedException("Subtable length of 0x80 not supported.") + End If + + length = RawData(offset) + End While + Return subTables + End Function + ''' + ''' Extracts the files contained within the ROMs. + ''' Extractions either run synchronously or asynchrounously, depending on the value of IsThreadSafe. + ''' + ''' Directory to store the extracted files. + ''' + Public Async Function Unpack(TargetDir As String, Provider As IOProvider) As Task + Dim fat = GetFAT() + + 'Set up extraction dependencies + CurrentExtractProgress = 0 + CurrentExtractMax = fat.Count + ExtractionTasks = New ConcurrentBag(Of Task) + + 'Ensure directory exists + If Not Provider.DirectoryExists(TargetDir) Then + Provider.CreateDirectory(TargetDir) + End If + + 'Start extracting + '-Header + Dim headerTask = Task.Run(New Action(Sub() + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "header.bin"), RawData(0, &H200)) + End Sub)) + If Me.IsThreadSafe Then + ExtractionTasks.Add(headerTask) + Else + Await headerTask + End If + + '-Arm9 + Dim arm9Task = Task.Run(New Action(Sub() + Dim arm9buffer As New List(Of Byte) + arm9buffer.AddRange(RawData(Me.Arm9RomOffset, Me.Arm9Size)) + + 'Write an additional 0xC bytes if the next 4 equal: 21 06 C0 DE + Dim footer As Int32 = Int32(Me.Arm9RomOffset + Me.Arm9Size) + If footer = &HDEC00621 Then + arm9buffer.AddRange(RawData(Me.Arm9RomOffset + Me.Arm9Size, &HC)) + End If + + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "arm9.bin"), arm9buffer.ToArray) + End Sub)) + If Me.IsThreadSafe Then + ExtractionTasks.Add(arm9Task) + Else + Await arm9Task + End If + + '-Arm7 + Dim arm7Task = Task.Run(New Action(Sub() + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "arm7.bin"), RawData(Me.Arm7RomOffset, Me.Arm7Size)) + End Sub)) + If Me.IsThreadSafe Then + ExtractionTasks.Add(arm7Task) + Else + Await arm7Task + End If + + '-Arm9 overlay table (y9.bin) + Dim y9Task = Task.Run(New Action(Sub() + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "y9.bin"), RawData(Me.FileArm9OverlayOffset, Me.FileArm9OverlaySize)) + End Sub)) + + If Me.IsThreadSafe Then + ExtractionTasks.Add(y9Task) + Else + Await y9Task + End If + + '-Extract arm7 overlay table (y7.bin) + Dim y7Task = Task.Run(New Action(Sub() + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "y7.bin"), RawData(Me.FileArm7OverlayOffset, Me.FileArm7OverlaySize)) + End Sub)) + + If Me.IsThreadSafe Then + ExtractionTasks.Add(y7Task) + Else + Await y7Task + End If + '-Extract overlays + Dim overlay9 = ExtractOverlay(fat, ParseArm9OverlayTable, IO.Path.Combine(TargetDir, "overlay"), Provider) + + If Me.IsThreadSafe Then + ExtractionTasks.Add(overlay9) + Else + Await overlay9 + End If + + Dim overlay7 = ExtractOverlay(fat, ParseArm7OverlayTable, IO.Path.Combine(TargetDir, "overlay7"), Provider) + + If Me.IsThreadSafe Then + ExtractionTasks.Add(overlay7) + Else + Await overlay7 + End If + '-Extract icon (banner.bin) + Dim iconTask = Task.Run(Sub() + If IconTitleOffset > 0 Then '0 means none + Provider.WriteAllBytes(IO.Path.Combine(TargetDir, "banner.bin"), RawData(IconTitleOffset, IconTitleLength)) + End If + End Sub) + + If Me.IsThreadSafe Then + ExtractionTasks.Add(iconTask) + Else + Await iconTask + End If + + '- Extract files + Dim filesExtraction = ExtractFiles(fat, GetFNT, TargetDir, Provider) + If Me.IsThreadSafe Then + ExtractionTasks.Add(filesExtraction) + Else + Await filesExtraction + End If + + 'Wait for everything to finish + Await Task.WhenAll(ExtractionTasks) + End Function + + ''' + ''' Extracts contained files if the file is thread safe, otherwise, extracts files one at a time. + ''' + ''' + ''' + ''' + ''' + Private Async Function ExtractFiles(fat As List(Of FileAllocationEntry), root As FilenameTable, targetDir As String, provider As IOProvider) As Task + Dim dest As String = IO.Path.Combine(targetDir, root.Name) + Dim f As New AsyncFor + f.RunSynchronously = Not Me.IsThreadSafe + f.BatchSize = root.Children.Count + Await (f.RunForEach(Async Function(item As FilenameTable) As Task + If item.IsDirectory Then + Await ExtractFiles(fat, item, dest, provider) + Else + Dim entry = fat(item.FileIndex) + Dim parentDir = IO.Path.GetDirectoryName(IO.Path.Combine(dest, item.Name)) + If Not provider.DirectoryExists(parentDir) Then + provider.CreateDirectory(parentDir) + End If + provider.WriteAllBytes(IO.Path.Combine(dest, item.Name), RawData(entry.Offset, entry.EndAddress - entry.Offset)) + System.Threading.Interlocked.Increment(CurrentExtractProgress) + End If + End Function, root.Children)) + End Function + + Private Async Function ExtractOverlay(fAT As List(Of FileAllocationEntry), overlayTable As List(Of OverlayTableEntry), targetDir As String, provider As IOProvider) As Task + If overlayTable.Count > 0 AndAlso Not provider.DirectoryExists(targetDir) Then + provider.CreateDirectory(targetDir) + End If + Dim f As New AsyncFor + f.RunSynchronously = Not Me.IsThreadSafe + f.BatchSize = overlayTable.Count + Await f.RunForEach(Sub(item As OverlayTableEntry) + Dim dest = IO.Path.Combine(targetDir, "overlay_" & item.OverlayID.ToString.PadLeft(4, "0"c) & ".bin") + Dim entry = fAT(item.FileID) + provider.WriteAllBytes(dest, RawData(entry.Offset, entry.EndAddress - entry.Offset)) + End Sub, overlayTable) + End Function + + ''' + ''' Gets or sets the total number of files extracted in the current unpacking process. + ''' + ''' + Private Property CurrentExtractProgress As Integer + Get + Return _extractProgress + End Get + Set(value As Integer) + _extractProgress = value + RaiseEvent UnpackProgress(Me, New ProgressReportedEventArgs With {.IsIndeterminate = False, .Progress = ExtractionProgress}) + End Set + End Property + Dim _extractProgress As Integer + + ''' + ''' Gets or sets the total number of files in the current unpacking process. + ''' + ''' + Private Property CurrentExtractMax As Integer + + ''' + ''' Currently running tasks that are part of the unpacking process. + ''' + ''' + Private Property ExtractionTasks As ConcurrentBag(Of Task) + + ''' + ''' The progress of the current unpacking process. + ''' + ''' + Public ReadOnly Property ExtractionProgress As Single Implements IReportProgress.Progress + Get + Return CurrentExtractProgress / CurrentExtractMax + End Get + End Property + + Private ReadOnly Property IsExtractionIndeterminate As Boolean Implements IReportProgress.IsIndeterminate + Get + Return False + End Get + End Property + + Private ReadOnly Property IsCompleted As Boolean Implements IReportProgress.IsCompleted + Get + Return CurrentExtractProgress = 1 + End Get + End Property + + Public ReadOnly Property Message As String Implements IReportProgress.Message + Get + Return Nothing + End Get + End Property #End Region - Public Overridable Function IsFileOfType(File As GenericFile) As Task(Of Boolean) ' Implements IDetectableFileType.IsOfType - Return Task.FromResult(File.Length > &H15D AndAlso File.RawData(&H15C) = &H56 AndAlso File.RawData(&H15D) = &HCF) - End Function + Public Overridable Function IsFileOfType(file As GenericFile) As Task(Of Boolean) Implements IDetectableFileType.IsOfType + Return Task.FromResult(file.Length > &H15D AndAlso file.RawData(&H15C) = &H56 AndAlso file.RawData(&H15D) = &HCF) + End Function - End Class -End Namespace \ No newline at end of file +End Class diff --git a/DotNet3dsToolkit/Misc/IOProvider.vb b/DotNet3dsToolkit/Misc/IOProvider.vb deleted file mode 100644 index 4bdb7dc..0000000 --- a/DotNet3dsToolkit/Misc/IOProvider.vb +++ /dev/null @@ -1,137 +0,0 @@ -Imports System.IO - -Namespace Misc - Public MustInherit Class IOProvider - ''' - ''' Gets the length, in bytes, of the file at the given path. - ''' - ''' Full path of the file. - ''' The length, in bytes, of the file - Public MustOverride Function GetFileLength(filename As String) As Long - - ''' - ''' Determines whether the specified file exists. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function FileExists(filename As String) As Boolean - - ''' - ''' Determines whether the specified directory exists. - ''' - ''' Full path of the directory. - ''' - Public MustOverride Function DirectoryExists(path As String) As Boolean - - ''' - ''' Creates a directory at the specified path. - ''' - ''' - Public MustOverride Sub CreateDirectory(path As String) - - ''' - ''' Gets the full paths of the files in the directory at the given path. - ''' - ''' Full path of the directory from which to get the files. - ''' The search string to match against the names of files in path. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions. - ''' True to search only the top directory. False to search all child directories too. - ''' - Public MustOverride Function GetFiles(path As String, searchPattern As String, topDirectoryOnly As Boolean) As String() - - ''' - ''' Gets the full paths of the directories in the directory at the given path - ''' - ''' Full path of the directory from which to get the files. - ''' True to search only the top directory. False to search all child directories too. - ''' - Public MustOverride Function GetDirectories(path As String, topDirectoryOnly As Boolean) As String() - - ''' - ''' Reads a file from disk, and returns its contents as a byte array. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function ReadAllBytes(filename As String) As Byte() - - ''' - ''' Writes the given text to disk. - ''' - ''' Full path of the file. - ''' File contents to be written. - Public MustOverride Sub WriteAllText(filename As String, Data As String) - - ''' - ''' Reads a file from disk, and returns its contents as a string. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function ReadAllText(filename As String) As String - - ''' - ''' Writes the given byte array to disk. - ''' - ''' Full path of the file. - ''' File contents to be written. - Public MustOverride Sub WriteAllBytes(filename As String, data As Byte()) - - ''' - ''' Copies a file, overwriting the destination file if it exists. - ''' - ''' - ''' - Public MustOverride Sub CopyFile(sourceFilename As String, destinationFilename As String) - - ''' - ''' Deletes the file at the given path. - ''' - ''' Full path of the file. - Public MustOverride Sub DeleteFile(filename As String) - - ''' - ''' Deletes the directory at the given path, and all of its contents. - ''' - ''' - Public MustOverride Sub DeleteDirectory(path As String) - - ''' - ''' Creates a temporary, blank file and returns its full path. - ''' - ''' - Public MustOverride Function GetTempFilename() As String - - ''' - ''' Creates a temporary empty directory and returns its full path. - ''' - ''' - Public MustOverride Function GetTempDirectory() As String - - ''' - ''' Determines whether or not a file of the given size will fit in memory. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function CanLoadFileInMemory(fileSize As Long) As Boolean - - ''' - ''' Opens a file stream with Read/Write privilages. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function OpenFile(filename As String) As Stream - - ''' - ''' Opens a file stream with Read privilages. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function OpenFileReadOnly(filename As String) As Stream - - ''' - ''' Opens a file stream with Write privilages. - ''' - ''' Full path of the file. - ''' - Public MustOverride Function OpenFileWriteOnly(filename As String) As Stream - End Class - -End Namespace diff --git a/DotNet3dsToolkit/Misc/LoadingStatusChangedEventArgs.vb b/DotNet3dsToolkit/Misc/LoadingStatusChangedEventArgs.vb deleted file mode 100644 index 9d42f74..0000000 --- a/DotNet3dsToolkit/Misc/LoadingStatusChangedEventArgs.vb +++ /dev/null @@ -1,10 +0,0 @@ -Namespace Misc - Public Class LoadingStatusChangedEventArgs - Inherits EventArgs - Public Property Complete As Boolean - Public Property Progress As Single - Public Property Completed As Integer - Public Property Total As Integer - End Class -End Namespace - diff --git a/DotNet3dsToolkit/Misc/UnpackProgressEventArgs.vb b/DotNet3dsToolkit/Misc/UnpackProgressEventArgs.vb deleted file mode 100644 index a778f6c..0000000 --- a/DotNet3dsToolkit/Misc/UnpackProgressEventArgs.vb +++ /dev/null @@ -1,7 +0,0 @@ -Namespace Misc - Public Class UnpackProgressEventArgs - Public Property FilesExtracted As Integer - Public Property TotalFiles As Integer - Public Property Progress As Single - End Class -End Namespace \ No newline at end of file diff --git a/DotNet3dsToolkit/Misc/WindowsIOProvider.vb b/DotNet3dsToolkit/Misc/WindowsIOProvider.vb index b3bee84..7e1f071 100644 --- a/DotNet3dsToolkit/Misc/WindowsIOProvider.vb +++ b/DotNet3dsToolkit/Misc/WindowsIOProvider.vb @@ -1,5 +1,6 @@ Imports System.IO Imports Microsoft.VisualBasic.Devices +Imports SkyEditor.Core.IO Namespace Misc Public Class WindowsIOProvider diff --git a/DotNet3dsToolkit/packages.config b/DotNet3dsToolkit/packages.config new file mode 100644 index 0000000..1a5f95e --- /dev/null +++ b/DotNet3dsToolkit/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ToolkitConsole/ToolkitConsole.vbproj b/ToolkitConsole/ToolkitConsole.vbproj index d9a5417..c0bc7c6 100644 --- a/ToolkitConsole/ToolkitConsole.vbproj +++ b/ToolkitConsole/ToolkitConsole.vbproj @@ -47,6 +47,18 @@ On + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\SkyEditor.Core.4.0.7\lib\portable45-net45+win8+wpa81\SkyEditor.Core.dll + True + @@ -104,6 +116,7 @@ Settings.Designer.vb + diff --git a/ToolkitConsole/packages.config b/ToolkitConsole/packages.config new file mode 100644 index 0000000..1a5f95e --- /dev/null +++ b/ToolkitConsole/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ToolkitForm/Form1.vb b/ToolkitForm/Form1.vb index 25b759f..9f08376 100644 --- a/ToolkitForm/Form1.vb +++ b/ToolkitForm/Form1.vb @@ -2,6 +2,7 @@ Imports System.IO Imports DotNet3dsToolkit Imports DotNet3dsToolkit.Misc +Imports SkyEditor.Core.Utilities Public Class Form1 @@ -82,16 +83,16 @@ Public Class Form1 End If End Sub - Private Sub OnUnpackProgressedInternal(sender As Object, e As UnpackProgressEventArgs) + Private Sub OnUnpackProgressedInternal(sender As Object, e As ProgressReportedEventArgs) If pbProgress.Style = ProgressBarStyle.Marquee Then pbProgress.Style = ProgressBarStyle.Continuous End If pbProgress.Value = e.Progress * pbProgress.Maximum - lblStatus.Text = String.Format("Extracting... ({0} of {1})", e.FilesExtracted, e.TotalFiles) + lblStatus.Text = String.Format("Extracting...") End Sub - Private Sub OnUnpackProgressed(sender As Object, e As UnpackProgressEventArgs) + Private Sub OnUnpackProgressed(sender As Object, e As ProgressReportedEventArgs) If InvokeRequired Then Invoke(Sub() OnUnpackProgressedInternal(sender, e)) Else diff --git a/ToolkitForm/ToolkitForm.vbproj b/ToolkitForm/ToolkitForm.vbproj index cfb67da..d7e8ea3 100644 --- a/ToolkitForm/ToolkitForm.vbproj +++ b/ToolkitForm/ToolkitForm.vbproj @@ -47,6 +47,18 @@ On + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\SkyEditor.Core.4.0.7\lib\portable45-net45+win8+wpa81\SkyEditor.Core.dll + True + @@ -127,6 +139,7 @@ Settings.Designer.vb + diff --git a/ToolkitForm/packages.config b/ToolkitForm/packages.config new file mode 100644 index 0000000..1a5f95e --- /dev/null +++ b/ToolkitForm/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file