From 3b9a1e736259274f095968ad5d9fbe52d8dd0b55 Mon Sep 17 00:00:00 2001 From: Brandon Wilson Date: Fri, 26 Sep 2014 14:30:06 -0400 Subject: [PATCH] Adding all the stuffs --- .gitignore | 8 + DriveCom/DriveCom.sln | 20 + DriveCom/DriveCom.v11.suo | Bin 0 -> 19968 bytes DriveCom/DriveCom/App.config | 6 + DriveCom/DriveCom/DriveCom.csproj | 61 ++ DriveCom/DriveCom/DriveCom.csproj.user | 9 + DriveCom/DriveCom/PhisonDevice.cs | 374 ++++++++++++ DriveCom/DriveCom/PhisonDo.csproj.user | 6 + DriveCom/DriveCom/Properties/AssemblyInfo.cs | 36 ++ DriveCom/DriveCom/Startup.cs | 428 ++++++++++++++ EmbedPayload/EmbedPayload.exe.config | 6 + EmbedPayload/EmbedPayload.sln | 20 + EmbedPayload/EmbedPayload.v11.suo | Bin 0 -> 14336 bytes EmbedPayload/EmbedPayload.vshost.exe.config | 6 + EmbedPayload/EmbedPayload/App.config | 6 + EmbedPayload/EmbedPayload/EmbedPayload.csproj | 58 ++ .../EmbedPayload/Properties/AssemblyInfo.cs | 36 ++ EmbedPayload/EmbedPayload/Startup.cs | 92 +++ Injector/Injector.sln | 20 + Injector/Injector.v11.suo | Bin 0 -> 19968 bytes Injector/Injector/App.config | 6 + Injector/Injector/DoPatch.csproj.user | 6 + Injector/Injector/FirmwareImage.cs | 150 +++++ Injector/Injector/FirmwareSection.cs | 30 + Injector/Injector/Injector.csproj | 61 ++ Injector/Injector/Injector.csproj.user | 6 + Injector/Injector/Properties/AssemblyInfo.cs | 36 ++ Injector/Injector/Startup.cs | 559 ++++++++++++++++++ docs/PinsToShortUponPlugInForBootMode.jpg | Bin 0 -> 113825 bytes firmware/build.bat | 41 ++ firmware/control.c | 184 ++++++ firmware/defs.h | 202 +++++++ firmware/main.c | 170 ++++++ firmware/scsi.c | 158 +++++ firmware/test.bat | 2 + firmware/timers.c | 116 ++++ firmware/timers.h | 12 + firmware/usb.c | 518 ++++++++++++++++ firmware/usb.h | 22 + patch/base.c | 370 ++++++++++++ patch/build.bat | 50 ++ patch/defs.h | 287 +++++++++ templates/BNdummy.bin | Bin 0 -> 33792 bytes templates/FWdummy.bin | Bin 0 -> 205824 bytes 44 files changed, 4178 insertions(+) create mode 100644 .gitignore create mode 100644 DriveCom/DriveCom.sln create mode 100644 DriveCom/DriveCom.v11.suo create mode 100644 DriveCom/DriveCom/App.config create mode 100644 DriveCom/DriveCom/DriveCom.csproj create mode 100644 DriveCom/DriveCom/DriveCom.csproj.user create mode 100644 DriveCom/DriveCom/PhisonDevice.cs create mode 100644 DriveCom/DriveCom/PhisonDo.csproj.user create mode 100644 DriveCom/DriveCom/Properties/AssemblyInfo.cs create mode 100644 DriveCom/DriveCom/Startup.cs create mode 100644 EmbedPayload/EmbedPayload.exe.config create mode 100644 EmbedPayload/EmbedPayload.sln create mode 100644 EmbedPayload/EmbedPayload.v11.suo create mode 100644 EmbedPayload/EmbedPayload.vshost.exe.config create mode 100644 EmbedPayload/EmbedPayload/App.config create mode 100644 EmbedPayload/EmbedPayload/EmbedPayload.csproj create mode 100644 EmbedPayload/EmbedPayload/Properties/AssemblyInfo.cs create mode 100644 EmbedPayload/EmbedPayload/Startup.cs create mode 100644 Injector/Injector.sln create mode 100644 Injector/Injector.v11.suo create mode 100644 Injector/Injector/App.config create mode 100644 Injector/Injector/DoPatch.csproj.user create mode 100644 Injector/Injector/FirmwareImage.cs create mode 100644 Injector/Injector/FirmwareSection.cs create mode 100644 Injector/Injector/Injector.csproj create mode 100644 Injector/Injector/Injector.csproj.user create mode 100644 Injector/Injector/Properties/AssemblyInfo.cs create mode 100644 Injector/Injector/Startup.cs create mode 100644 docs/PinsToShortUponPlugInForBootMode.jpg create mode 100644 firmware/build.bat create mode 100644 firmware/control.c create mode 100644 firmware/defs.h create mode 100644 firmware/main.c create mode 100644 firmware/scsi.c create mode 100644 firmware/test.bat create mode 100644 firmware/timers.c create mode 100644 firmware/timers.h create mode 100644 firmware/usb.c create mode 100644 firmware/usb.h create mode 100644 patch/base.c create mode 100644 patch/build.bat create mode 100644 patch/defs.h create mode 100644 templates/BNdummy.bin create mode 100644 templates/FWdummy.bin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e1967b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +**/bin/Debug/* +**/obj/Debug/* +**/bin/Release/* +**/obj/Release/* +firmware/bin/* +patch/bin/* +patch/equates.h +tools/* diff --git a/DriveCom/DriveCom.sln b/DriveCom/DriveCom.sln new file mode 100644 index 0000000..be504e7 --- /dev/null +++ b/DriveCom/DriveCom.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2012 for Windows Desktop +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DriveCom", "DriveCom\DriveCom.csproj", "{25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DriveCom/DriveCom.v11.suo b/DriveCom/DriveCom.v11.suo new file mode 100644 index 0000000000000000000000000000000000000000..cfa5516516654fcf01ff43f0883d0ae06cf51996 GIT binary patch literal 19968 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 CFIHCo literal 0 HcmV?d00001 diff --git a/DriveCom/DriveCom/App.config b/DriveCom/DriveCom/App.config new file mode 100644 index 0000000..58262a1 --- /dev/null +++ b/DriveCom/DriveCom/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/DriveCom/DriveCom/DriveCom.csproj b/DriveCom/DriveCom/DriveCom.csproj new file mode 100644 index 0000000..13695be --- /dev/null +++ b/DriveCom/DriveCom/DriveCom.csproj @@ -0,0 +1,61 @@ + + + + + Debug + AnyCPU + {25CF4003-4DF8-4F3C-B9B5-50252C2CD2C2} + Exe + Properties + DriveCom + DriveCom + v4.0 + 512 + + + + AnyCPU + true + full + false + ..\..\tools\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + ..\..\tools\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DriveCom/DriveCom/DriveCom.csproj.user b/DriveCom/DriveCom/DriveCom.csproj.user new file mode 100644 index 0000000..9934927 --- /dev/null +++ b/DriveCom/DriveCom/DriveCom.csproj.user @@ -0,0 +1,9 @@ + + + + /drive=E /burner=C:\Users\Brandon\Documents\GitHub\PS2251-03\BINs\BN03V104M.BIN /firmware=C:\Users\Brandon\Documents\GitHub\PS2251-03\BINs\FW03FF01V10353M.BIN + + + /drive=E /burner="C:\Users\Brandon\Documents\GitHub\PS2251-03\BINs\BN03V104M.BIN" + + \ No newline at end of file diff --git a/DriveCom/DriveCom/PhisonDevice.cs b/DriveCom/DriveCom/PhisonDevice.cs new file mode 100644 index 0000000..1398604 --- /dev/null +++ b/DriveCom/DriveCom/PhisonDevice.cs @@ -0,0 +1,374 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace DriveCom +{ + public class PhisonDevice + { + private char _driveLetter; + private SafeFileHandle _handle; + + public enum RunMode + { + Unknown, + BootMode, + Burner, + HardwareVerify, + Firmware + } + + [Flags] + public enum EFileAttributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Directory = 0x00000010, + Archive = 0x00000020, + Device = 0x00000040, + Normal = 0x00000080, + Temporary = 0x00000100, + SparseFile = 0x00000200, + ReparsePoint = 0x00000400, + Compressed = 0x00000800, + Offline = 0x00001000, + NotContentIndexed = 0x00002000, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + FirstPipeInstance = 0x00080000 + } + + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern SafeFileHandle CreateFile( + string fileName, + [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess, + [MarshalAs(UnmanagedType.U4)] FileShare fileShare, + IntPtr securityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, + [MarshalAs(UnmanagedType.U4)] EFileAttributes flags, + IntPtr template); + + [DllImport("kernel32.dll")] + static public extern int CloseHandle(SafeFileHandle hObject); + + public const byte SCSI_IOCTL_DATA_OUT = 0; + public const byte SCSI_IOCTL_DATA_IN = 1; + + [StructLayout(LayoutKind.Sequential)] + class SCSI_PASS_THROUGH_DIRECT + { + private const int _CDB_LENGTH = 16; + + public short Length; + public byte ScsiStatus; + public byte PathId; + public byte TargetId; + public byte Lun; + public byte CdbLength; + public byte SenseInfoLength; + public byte DataIn; + public int DataTransferLength; + public int TimeOutValue; + public IntPtr DataBuffer; + public uint SenseInfoOffset; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = _CDB_LENGTH)] + public byte[] Cdb; + + public SCSI_PASS_THROUGH_DIRECT() + { + Cdb = new byte[_CDB_LENGTH]; + } + }; + + [StructLayout(LayoutKind.Sequential)] + class SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER + { + private const int _SENSE_LENGTH = 32; + internal SCSI_PASS_THROUGH_DIRECT sptd = new SCSI_PASS_THROUGH_DIRECT(); + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = _SENSE_LENGTH)] + internal byte[] sense; + + public SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER() + { + sense = new byte[_SENSE_LENGTH]; + } + }; + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, + IntPtr lpInBuffer, uint nInBufferSize, + IntPtr lpOutBuffer, uint nOutBufferSize, + out uint lpBytesReturned, IntPtr lpOverlapped); + + /// + /// Creates a reference to a device with a Phison USB controller. + /// + /// The Windows drive letter representing the device. + public PhisonDevice(char driveLetter) + { + _driveLetter = driveLetter; + } + + /// + /// Opens a connection to the device. + /// + public void Open() + { + _handle = CreateFile(string.Format("\\\\.\\{0}:", _driveLetter), FileAccess.ReadWrite, FileShare.ReadWrite, + IntPtr.Zero, FileMode.Open, EFileAttributes.NoBuffering, IntPtr.Zero); + } + + public byte[] RequestVendorInfo() + { + var data = SendCommand(new byte[] { 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, + 512 + 16); + byte[] ret = null; + + if (data != null) + { + ret = data.Take(512).ToArray(); + } + + return ret; + } + + public ushort? GetChipType() + { + ushort? ret = null; + var info = RequestVendorInfo(); + + if (info != null) + { + if (info[0x17A] == (byte)'V' && info[0x17B] == (byte)'R') + { + var data = info.Skip(0x17E).Take(2).ToArray(); + ret = (ushort)((data[0] << 8) | data[1]); + } + } + + return ret; + } + + public RunMode GetRunMode() + { + var ret = RunMode.Unknown; + var info = RequestVendorInfo(); + + if (info != null) + { + if (info[0x17A] == (byte)'V' && info[0x17B] == (byte)'R') + { + //TODO: Fix this, this is a dumb way of detecting it + switch (ASCIIEncoding.ASCII.GetString(info.Skip(0xA0).Take(8).ToArray())) + { + case " PRAM ": + ret = RunMode.BootMode; + break; + case " FW BURN": + ret = RunMode.Burner; + break; + case " HV TEST": + ret = RunMode.HardwareVerify; + break; + default: + ret = RunMode.Firmware; + break; + } + } + } + + return ret; + } + + public void JumpToPRAM() + { + SendCommand(new byte[] { 0x06, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + } + + public void JumpToBootMode() + { + SendCommand(new byte[] { 0x06, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + } + + public void TransferFile(byte[] data) + { + TransferFile(data, 0x03, 0x02); + } + + public void TransferFile(byte[] data, byte header, byte body) + { + var size = data.Length - 1024; + + //Send header + SendCommand(new byte[] { 0x06, 0xB1, header, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, data.Take(0x200).ToArray()); + + //Get response + var response = SendCommand(new byte[] { 0x06, 0xB0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 8); + if (response == null || response[0] != 0x55) + { + throw new InvalidOperationException("Header not accepted"); + } + + //Send body + int address = 0; + while (size > 0) + { + int chunkSize; + if (size > 0x8000) + { + chunkSize = 0x8000; + } + else + { + chunkSize = size; + } + + int cmdAddress = address >> 9; + int cmdChunk = chunkSize >> 9; + SendCommand(new byte[] { 0x06, 0xB1, body, (byte)((cmdAddress >> 8) & 0xFF), (byte)(cmdAddress & 0xFF), + 0x00, 0x00, (byte)((cmdChunk >> 8) & 0xFF), (byte)(cmdChunk & 0xFF), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + data.Skip(address + 0x200).Take(chunkSize).ToArray()); + + //Get response + var r = SendCommand(new byte[] { 0x06, 0xB0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 8); + if (r == null || r[0] != 0xA5) + { + throw new InvalidOperationException("Body not accepted"); + } + + address += chunkSize; + size -= chunkSize; + } + } + + /// + /// Sends command with no attached data and returns expected response. + /// + /// + /// + /// + public byte[] SendCommand(byte[] cmd, int bytesExpected) + { + return _SendCommand(_handle, cmd, null, bytesExpected); + } + + /// + /// Sends command with no attached data and no response. + /// + /// + public void SendCommand(byte[] cmd) + { + SendCommand(cmd, null); + } + + /// + /// Sends command with attached data and no response. + /// + /// + /// + public void SendCommand(byte[] cmd, byte[] data) + { + _SendCommand(_handle, cmd, data, 0); + } + + /// + /// Closes the connection to the device. + /// + public void Close() + { + if (_handle != null && !_handle.IsClosed) + { + _handle.Close(); + } + } + + private static byte[] _SendCommand(SafeFileHandle handle, byte[] cmd, byte[] data, int bytesExpected) + { + const int IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x4D014; + const int TIMEOUT_SECS = 30; + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER scsi = null; + IntPtr inBuffer = IntPtr.Zero; + byte[] ret = null; + + try + { + scsi = new SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER(); + scsi.sptd.Length = (short)Marshal.SizeOf(scsi.sptd); + scsi.sptd.TimeOutValue = TIMEOUT_SECS; + scsi.sptd.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER), "sense"); + scsi.sptd.SenseInfoLength = (byte)scsi.sense.Length; + scsi.sptd.CdbLength = (byte)cmd.Length; + Array.Copy(cmd, scsi.sptd.Cdb, cmd.Length); + scsi.sptd.DataIn = data != null && data.Length > 0 ? SCSI_IOCTL_DATA_OUT : SCSI_IOCTL_DATA_IN; + scsi.sptd.DataTransferLength = data != null && data.Length > 0 ? data.Length : bytesExpected; + scsi.sptd.DataBuffer = Marshal.AllocHGlobal(scsi.sptd.DataTransferLength); + if (data != null && data.Length > 0) + { + Marshal.Copy(data, 0, scsi.sptd.DataBuffer, data.Length); + } + + uint bytesReturned; + inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(scsi)); + var size = (uint)Marshal.SizeOf(scsi); + Marshal.StructureToPtr(scsi, inBuffer, false); + if (!DeviceIoControl(handle.DangerousGetHandle(), IOCTL_SCSI_PASS_THROUGH_DIRECT, + inBuffer, size, inBuffer, size, out bytesReturned, IntPtr.Zero)) + { + //Whoops, do something with the error code + int last = Marshal.GetLastWin32Error(); + throw new InvalidOperationException("DeviceIoControl failed: " + last.ToString("X04")); + } + else + { + if (scsi.sptd.ScsiStatus != 0) + { + //Whoops, do something with the error code + throw new InvalidOperationException("SCSI command failed: " + scsi.sptd.ScsiStatus.ToString("X02")); + } + else + { + //Success, marshal back any data we received + if (scsi.sptd.DataTransferLength > 0) + { + ret = new byte[scsi.sptd.DataTransferLength]; + Marshal.Copy(scsi.sptd.DataBuffer, ret, 0, ret.Length); + } + } + } + } + finally + { + /* Free any unmanaged resources */ + + if (scsi != null && scsi.sptd.DataBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(scsi.sptd.DataBuffer); + } + + if (inBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(inBuffer); + } + } + + return ret; + } + } +} diff --git a/DriveCom/DriveCom/PhisonDo.csproj.user b/DriveCom/DriveCom/PhisonDo.csproj.user new file mode 100644 index 0000000..7b718e3 --- /dev/null +++ b/DriveCom/DriveCom/PhisonDo.csproj.user @@ -0,0 +1,6 @@ + + + + /drive=E /burner="C:\\Users\\Brandon\\Documents\GitHub\\PS2251-03\\BINs\BN03V104M.bin" /firmware="C:\\Users\\Brandon\\Documents\\GitHub\\PS2251-03\\patch\\bin\\patched.bin" + + \ No newline at end of file diff --git a/DriveCom/DriveCom/Properties/AssemblyInfo.cs b/DriveCom/DriveCom/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..96d2c25 --- /dev/null +++ b/DriveCom/DriveCom/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PhisonDo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PhisonDo")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ebfd9a94-1fd1-4595-a864-a3525ff4c875")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DriveCom/DriveCom/Startup.cs b/DriveCom/DriveCom/Startup.cs new file mode 100644 index 0000000..a54a342 --- /dev/null +++ b/DriveCom/DriveCom/Startup.cs @@ -0,0 +1,428 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace DriveCom +{ + class Startup + { + private const int _WAIT_TIME_MS = 2000; + private static PhisonDevice _device = null; + private static string _burner; + private static string _firmware; + private static string _password; + + public enum Action + { + None, + GetInfo, + SetPassword, + DumpFirmware, + SetBootMode, + SendExecutable, + SendFirmware + } + + public enum ExitCode + { + Success = 0, + Failure = 1 + } + + static void Main(string[] args) + { + try + { + Environment.ExitCode = (int)ExitCode.Success; + + var action = Action.None; + string drive = string.Empty; + + foreach (var arg in args) + { + var parts = arg.TrimStart(new char[] { '/' }).Split(new char[] { '=' }, + StringSplitOptions.RemoveEmptyEntries); + switch (parts[0].ToLower()) + { + case "action": + { + action = (Action)Enum.Parse(typeof(Action), parts[1]); + break; + } + case "drive": + { + drive = parts[1]; + break; + } + case "burner": + { + _burner = parts[1]; + break; + } + case "firmware": + { + _firmware = parts[1]; + break; + } + case "password": + { + _password = parts[1]; + break; + } + default: + { + break; + } + } + } + + if (!string.IsNullOrEmpty(drive)) + { + _OpenDrive(drive); + } + + if (action != Action.None) + { + Console.WriteLine("Action specified: " + action.ToString()); + + switch (action) + { + case Action.DumpFirmware: + { + _DumpFirmware(_firmware); + break; + } + case Action.GetInfo: + { + _GetInfo(); + break; + } + case Action.SendExecutable: + { + _ExecuteImage(_burner); + break; + } + case Action.SendFirmware: + { + _SendFirmware(); + break; + } + case Action.SetBootMode: + { + _device.JumpToBootMode(); + Thread.Sleep(_WAIT_TIME_MS); + break; + } + case Action.SetPassword: + { + _SendPassword(_password); + break; + } + default: + { + throw new ArgumentException("No/invalid action specified"); + } + } + } + else + { + Console.WriteLine("No action specified, entering console."); + + bool exiting = false; + while (!exiting) + { + Console.Write(">"); + var line = Console.ReadLine(); + var @params = line.Split(new char[] { ' ' }); + + try + { + switch (@params[0].ToLower()) + { + case "open": + { + _OpenDrive(@params[1]); + break; + } + case "close": + { + _CloseDrive(); + break; + } + case "mode": + { + Console.WriteLine("Mode: " + _GetInfo().ToString()); + break; + } + case "info": + { + var data = _device.RequestVendorInfo(); + Console.WriteLine(string.Format("Info: {0}...", BitConverter.ToString(data, 0, 16))); + break; + } + case "password": + { + _SendPassword(@params[1]); + break; + } + case "dump_xram": + { + var address = 0; + var data = new byte[0xF000]; + for (int i = 0; i < data.Length; i++) + { + var result = _device.SendCommand(new byte[] { 0x06, 0x06, + (byte)((address >> 8) & 0xFF), (byte)(address & 0xFF), 0x00, 0x00, 0x00, 0x00 }, 1); + data[address] = result[0]; + address++; + } + + File.WriteAllBytes(@params[1], data); + break; + } + case "dump_firmware": + { + _DumpFirmware(@params[1]); + break; + } + case "nand_read": + { + var address = Convert.ToInt32(@params[1], 16); + var size = Convert.ToInt32(@params[2], 16); + var result = _device.SendCommand(new byte[] { 0x06, 0xB2, 0x10, + (byte)((address >> 8) & 0xFF), (byte)(address & 0xFF), 0x00, 0x00, + (byte)((size >> 8) & 0xFF), (byte)(size & 0xFF) }, size * 512); + Console.WriteLine(string.Format("Data: {0}...", BitConverter.ToString(result, 0, 16))); + break; + } + case "boot": + { + _device.JumpToBootMode(); + Thread.Sleep(_WAIT_TIME_MS); + break; + } + case "set_burner": + { + _burner = @params[1]; + break; + } + case "set_firmware": + { + _firmware = @params[1]; + break; + } + case "burner": + { + _ExecuteImage(_burner); + break; + } + case "firmware": + { + _SendFirmware(); + break; + } + case "peek": + { + var address = Convert.ToInt32(@params[1], 16); + var result = _device.SendCommand(new byte[] { 0x06, 0x06, + (byte)((address >> 8) & 0xFF), (byte)(address & 0xFF), 0x00, 0x00, 0x00, 0x00 }, 1); + Console.WriteLine("Value: " + result[0].ToString("X02")); + break; + } + case "poke": + { + var address = Convert.ToInt32(@params[1], 16); + var value = Convert.ToInt32(@params[2], 16); + _device.SendCommand(new byte[] { 0x06, 0x07, + (byte)((address >> 8) & 0xFF), (byte)(address & 0xFF), (byte)value, 0x00, 0x00 }, 1); + break; + } + case "ipeek": + { + var address = Convert.ToInt32(@params[1], 16); + var result = _device.SendCommand(new byte[] { 0x06, 0x08, + (byte)(address & 0xFF), 0x00, 0x00, 0x00, 0x00 }, 1); + Console.WriteLine("Value: " + result[0].ToString("X02")); + break; + } + case "ipoke": + { + var address = Convert.ToInt32(@params[1], 16); + var value = Convert.ToInt32(@params[2], 16); + _device.SendCommand(new byte[] { 0x06, 0x09, + (byte)(address & 0xFF), (byte)value, 0x00, 0x00 }, 1); + break; + } + case "quit": + case "exit": + { + exiting = true; + break; + } + default: + Console.WriteLine("Invalid command: " + @params[0]); + break; + } + } + catch (Exception ex) + { + Console.WriteLine("ERROR: " + ex.ToString()); + } + } + + Console.WriteLine("Done."); + } + } + catch (Exception ex) + { + Environment.ExitCode = (int)ExitCode.Failure; + + Console.WriteLine("FATAL: " + ex.ToString()); + } + finally + { + if (_device != null) + { + _device.Close(); + } + } + } + + private static void _OpenDrive(string drive) + { + _CloseDrive(); + + _device = new PhisonDevice(drive[0]); + _device.Open(); + } + + private static void _CloseDrive() + { + if (_device != null) + { + _device.Close(); + _device = null; + } + } + + private static void _DumpFirmware(string fileName) + { + var address = 0; + var data = new byte[0x32400]; + var header = new byte[] { 0x42, 0x74, 0x50, 0x72, 0x61, 0x6D, 0x43, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x10, 0x0B, 0x18 }; + Array.Copy(header, 0, data, 0, header.Length); + while (address * 0x200 < data.Length) + { + var length = Math.Min(0x40 * 512, (data.Length - 0x400) - (address * 0x200)); + var temp = length / 512; + var result = _device.SendCommand(new byte[] { 0x06, 0xB2, 0x10, + (byte)((address >> 8) & 0xFF), (byte)(address & 0xFF), 0x00, 0x00, + (byte)((temp >> 8) & 0xFF), (byte)(temp & 0xFF) }, length); + Array.Copy(result.Take(length).ToArray(), 0, data, 0x200 + address * 512, length); + address += 0x40; + } + + var footer = new byte[] { 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6D, + 0x70, 0x20, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x03, 0x01, 0x00, 0x10, 0x01, 0x04, 0x10, 0x42 }; + Array.Copy(footer, 0, data, data.Length - 0x200, footer.Length); + File.WriteAllBytes(fileName, data); + } + + private static void _SendPassword(string password) + { + var data = new byte[0x200]; + var pw = ASCIIEncoding.ASCII.GetBytes(password); + Array.Copy(pw, 0, data, 0x10, pw.Length); + _device.SendCommand(new byte[] { 0x0E, 0x00, 0x01, 0x55, 0xAA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, data); + } + + private static void _SendFirmware() + { + var mode = _GetInfo(); + if (mode != PhisonDevice.RunMode.Burner) + { + if (mode != PhisonDevice.RunMode.BootMode) + { + Console.WriteLine("Switching to boot mode..."); + _device.JumpToBootMode(); + Thread.Sleep(_WAIT_TIME_MS); + } + + _ExecuteImage(_burner); + } + + _RunFirmware(_firmware); + } + + private static PhisonDevice.RunMode _GetInfo() + { + Console.WriteLine("Gathering information..."); + Console.WriteLine("Reported chip type: " + _device.GetChipType().GetValueOrDefault().ToString("X04")); + + var ret = _device.GetRunMode(); + Console.WriteLine("Mode: " + ret.ToString()); + + return ret; + } + + private static void _ExecuteImage(string fileName) + { + //Read image + var file = new FileStream(fileName, FileMode.Open); + var fileData = new byte[file.Length]; + file.Read(fileData, 0, fileData.Length); + file.Close(); + + //Load it + _device.TransferFile(fileData); + _device.JumpToPRAM(); + + //Wait a little bit + Thread.Sleep(_WAIT_TIME_MS); + } + + private static void _RunFirmware(string fileName) + { + //Get file data + var fw = new FileStream(fileName, FileMode.Open); + var data = new byte[fw.Length]; + fw.Read(data, 0, data.Length); + fw.Close(); + + //TODO: Find out what this actually does... + //Console.WriteLine("Sending scary B7 command (takes several seconds)..."); + //_device.SendCommand(new byte[] { 0x06, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + + Console.WriteLine("Rebooting..."); + _device.JumpToBootMode(); + Thread.Sleep(_WAIT_TIME_MS); + + Console.WriteLine("Sending firmware..."); + _device.TransferFile(data, 0x01, 0x00); + var ret = _device.SendCommand(new byte[] { 0x06, 0xEE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 64 + 8); + Thread.Sleep(_WAIT_TIME_MS); + _device.TransferFile(data, 0x03, 0x02); + ret = _device.SendCommand(new byte[] { 0x06, 0xEE, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, 64 + 8); + Thread.Sleep(_WAIT_TIME_MS); + ret = _device.SendCommand(new byte[] { 0x06, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 64 + 8); + Thread.Sleep(_WAIT_TIME_MS); + ret = _device.SendCommand(new byte[] { 0x06, 0xEE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, 64 + 8); + Thread.Sleep(_WAIT_TIME_MS); + + Console.WriteLine("Executing..."); + _device.JumpToPRAM(); + Thread.Sleep(_WAIT_TIME_MS); + + //Display new mode, if we can actually get it + Console.WriteLine("Mode: " + _device.GetRunMode().ToString()); + } + } +} diff --git a/EmbedPayload/EmbedPayload.exe.config b/EmbedPayload/EmbedPayload.exe.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/EmbedPayload/EmbedPayload.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/EmbedPayload/EmbedPayload.sln b/EmbedPayload/EmbedPayload.sln new file mode 100644 index 0000000..d41bf9a --- /dev/null +++ b/EmbedPayload/EmbedPayload.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2012 for Windows Desktop +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbedPayload", "EmbedPayload\EmbedPayload.csproj", "{E2381629-180B-44DE-8702-B63B4699A587}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E2381629-180B-44DE-8702-B63B4699A587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2381629-180B-44DE-8702-B63B4699A587}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2381629-180B-44DE-8702-B63B4699A587}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2381629-180B-44DE-8702-B63B4699A587}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EmbedPayload/EmbedPayload.v11.suo b/EmbedPayload/EmbedPayload.v11.suo new file mode 100644 index 0000000000000000000000000000000000000000..47ca1416d38e36fe9d9b34b3fd1c4618bcdcd900 GIT binary patch literal 14336 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 literal 0 HcmV?d00001 diff --git a/EmbedPayload/EmbedPayload.vshost.exe.config b/EmbedPayload/EmbedPayload.vshost.exe.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/EmbedPayload/EmbedPayload.vshost.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/EmbedPayload/EmbedPayload/App.config b/EmbedPayload/EmbedPayload/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/EmbedPayload/EmbedPayload/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/EmbedPayload/EmbedPayload/EmbedPayload.csproj b/EmbedPayload/EmbedPayload/EmbedPayload.csproj new file mode 100644 index 0000000..ffede20 --- /dev/null +++ b/EmbedPayload/EmbedPayload/EmbedPayload.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {E2381629-180B-44DE-8702-B63B4699A587} + Exe + Properties + EmbedPayload + EmbedPayload + v4.5 + 512 + + + AnyCPU + true + full + false + ..\..\tools\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\..\tools\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EmbedPayload/EmbedPayload/Properties/AssemblyInfo.cs b/EmbedPayload/EmbedPayload/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..612a796 --- /dev/null +++ b/EmbedPayload/EmbedPayload/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EmbedPayload")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Toshiba")] +[assembly: AssemblyProduct("EmbedPayload")] +[assembly: AssemblyCopyright("Copyright © Toshiba 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5cd1f91a-6190-490e-a5c8-6d200a7e00db")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/EmbedPayload/EmbedPayload/Startup.cs b/EmbedPayload/EmbedPayload/Startup.cs new file mode 100644 index 0000000..53e33d6 --- /dev/null +++ b/EmbedPayload/EmbedPayload/Startup.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EmbedPayload +{ + class Startup + { + public enum ExitCode + { + Success = 0, + Failure = 1 + } + + static void Main(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine("Usage: [payload BIN] [firmware image]"); + return; + } + + try + { + //Assume success at first + Environment.ExitCode = (int)ExitCode.Success; + + //Read all bytes from input file + var payload = File.ReadAllBytes(args[0]); + + //Read all bytes from output file: + var stream = new FileStream(args[1], FileMode.Open, FileAccess.ReadWrite); + var header = new byte[0x200]; + stream.Read(header, 0, header.Length); + var data = new byte[0x6000]; + stream.Read(data, 0, data.Length); + + // Look for 0x12345678 + var signature = new byte[] { 0x12, 0x34, 0x56, 0x78 }; + int? address = null; + for (int i = 0; i < data.Length; i++) + { + bool match = true; + for (int j = 0; j < signature.Length; j++) + { + if (data[i + j] != signature[j]) + { + match = false; + break; + } + } + + if (match) + { + address = i; + break; + } + } + + // When found, overwrite with input data + if (address.HasValue) + { + if ((0x200 + address.Value) >= 0x6000) + { + throw new InvalidOperationException("Insufficient memory to inject file!"); + } + + stream.Seek(0x200 + address.Value, SeekOrigin.Begin); + stream.Write(payload, 0, payload.Length); + + //Save output file back out + stream.Close(); + Console.WriteLine("File updated."); + } + else + { + Console.WriteLine("Signature not found!"); + } + } + catch (Exception ex) + { + //Uh-oh + Environment.ExitCode = (int)ExitCode.Failure; + + Console.WriteLine("FATAL: " + ex.ToString()); + } + } + } +} diff --git a/Injector/Injector.sln b/Injector/Injector.sln new file mode 100644 index 0000000..5c45d9a --- /dev/null +++ b/Injector/Injector.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2012 for Windows Desktop +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Injector", "Injector\Injector.csproj", "{41480B6F-9051-43D1-9484-30A265D09D5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41480B6F-9051-43D1-9484-30A265D09D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41480B6F-9051-43D1-9484-30A265D09D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41480B6F-9051-43D1-9484-30A265D09D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41480B6F-9051-43D1-9484-30A265D09D5D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Injector/Injector.v11.suo b/Injector/Injector.v11.suo new file mode 100644 index 0000000000000000000000000000000000000000..cb55d50fd37f4e3aab671d4563c3c175104a7044 GIT binary patch literal 19968 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 bTY8<3wSZea(MMNJrOox<{ry|#t^fZAcNDoD literal 0 HcmV?d00001 diff --git a/Injector/Injector/App.config b/Injector/Injector/App.config new file mode 100644 index 0000000..58262a1 --- /dev/null +++ b/Injector/Injector/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Injector/Injector/DoPatch.csproj.user b/Injector/Injector/DoPatch.csproj.user new file mode 100644 index 0000000..0f80b6e --- /dev/null +++ b/Injector/Injector/DoPatch.csproj.user @@ -0,0 +1,6 @@ + + + + /firmware="C:\\Users\\Brandon\\Documents\\GitHub\\PS2251-03\\BINs\\FW03FF01V10353M.BIN" /basecode="C:\\Users\\Brandon\\Documents\\GitHub\\PS2251-03\\patch\\bin\\output.bin" /action=FindFreeBlocks /output="C:\\Users\\Brandon\\Documents\\GitHub\\PS2251-03\\patch\\bin\\patched.bin" /baserst="C:\\Users\\Brandon\\Documents\\GitHub\\PS2251-03\\patch\\bin\\main.rst" + + \ No newline at end of file diff --git a/Injector/Injector/FirmwareImage.cs b/Injector/Injector/FirmwareImage.cs new file mode 100644 index 0000000..b89eb11 --- /dev/null +++ b/Injector/Injector/FirmwareImage.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Injector +{ + public class FirmwareImage + { + private string _fileName; + private byte[] _header; + private Dictionary _sections; + private byte[] _footer; + + public FirmwareImage(string fileName) + { + _fileName = fileName; + _header = new byte[0x200]; + _sections = new Dictionary(); + _sections.Add(FirmwareSection.Base, new byte[0x6000]); + } + + public byte[] GetSection(FirmwareSection section) + { + byte[] ret = null; + + if (_sections.ContainsKey(section)) + { + ret = _sections[section]; + } + + return ret; + } + + public void Open() + { + FirmwareSection i = 0; + + //Get the header and base page + var stream = new FileStream(_fileName, FileMode.Open); + var @base = GetSection(FirmwareSection.Base); + stream.Read(_header, 0, _header.Length); + stream.Read(@base, 0, @base.Length); + + //Read in all the sections + while ((stream.Length - stream.Position) > 0x200) + { + var data = new byte[0x4000]; + stream.Read(data, 0, data.Length); + _sections.Add(i++, data); + } + + //If we have a footer, read it in + if ((stream.Length - stream.Position) == 0x200) + { + _footer = new byte[0x200]; + stream.Read(_footer, 0, _footer.Length); + } + + //All done + stream.Close(); + } + + public bool FindPattern(byte?[] pattern, out FirmwareSection section, out int address) + { + return FindPattern(pattern, 0, out section, out address); + } + + public bool FindPattern(byte?[] pattern, int startingOffset, out FirmwareSection section, out int address) + { + bool ret = false; + section = FirmwareSection.Base; + address = 0; + + foreach (var s in _sections) + { + for (int i = startingOffset; i < s.Value.Length; i++) + { + bool match = true; + for (int j = 0; j < pattern.Length; j++) + { + if (((i + j) >= s.Value.Length) || + ((s.Value[i + j] != pattern[j]) && (pattern[j].HasValue))) + { + match = false; + break; + } + } + + if (match) + { + section = s.Key; + address = i; + ret = true; + break; + } + } + + if (ret) + { + break; + } + } + + return ret; + } + + public int FindLastFreeChunk(FirmwareSection section) + { + int ret = -1; + + if (_sections.ContainsKey(section)) + { + var data = _sections[section]; + var repeating = data[data.Length - 1]; + ret = data.Length - 2; + + while (data[ret] == repeating) + { + ret--; + if (ret < 0) + { + break; + } + } + } + + return ++ret; + } + + public void Save(string fileName) + { + var output = new FileStream(fileName, FileMode.Create); + output.Write(_header, 0, _header.Length); + foreach (var section in _sections.OrderBy(t => t.Key)) + { + output.Write(section.Value, 0, section.Value.Length); + } + + if (_footer != null) + { + output.Write(_footer, 0, _footer.Length); + } + + output.Close(); + } + } +} diff --git a/Injector/Injector/FirmwareSection.cs b/Injector/Injector/FirmwareSection.cs new file mode 100644 index 0000000..40c4c9d --- /dev/null +++ b/Injector/Injector/FirmwareSection.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Injector +{ + public enum FirmwareSection + { + None = -2, + Base = -1, + Section0 = 0x00, + Section1 = 0x01, + Section2 = 0x02, + Section3 = 0x03, + Section4 = 0x04, + Section5 = 0x05, + Section6 = 0x06, + Section7 = 0x07, + Section8 = 0x08, + Section9 = 0x09, + SectionA = 0x0A, + SectionB = 0x0B, + SectionC = 0x0C, + SectionD = 0x0D, + SectionE = 0x0E, + SectionF = 0x0F + } +} diff --git a/Injector/Injector/Injector.csproj b/Injector/Injector/Injector.csproj new file mode 100644 index 0000000..c51311b --- /dev/null +++ b/Injector/Injector/Injector.csproj @@ -0,0 +1,61 @@ + + + + + Debug + AnyCPU + {41480B6F-9051-43D1-9484-30A265D09D5D} + Exe + Properties + Injector + Injector + v4.0 + 512 + + + + AnyCPU + true + full + false + ..\..\tools\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\..\tools\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Injector/Injector/Injector.csproj.user b/Injector/Injector/Injector.csproj.user new file mode 100644 index 0000000..44862ff --- /dev/null +++ b/Injector/Injector/Injector.csproj.user @@ -0,0 +1,6 @@ + + + + /firmware=C:\Users\Brandon\Documents\GitHub\PS2251-03\BINs\FW03FF01V10353M.BIN /output=free.txt /action=FindFreeBlock + + \ No newline at end of file diff --git a/Injector/Injector/Properties/AssemblyInfo.cs b/Injector/Injector/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..34d8819 --- /dev/null +++ b/Injector/Injector/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DoPatch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Toshiba")] +[assembly: AssemblyProduct("DoPatch")] +[assembly: AssemblyCopyright("Copyright © Toshiba 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("16a14751-01cb-4cb0-a3d1-7835d24f4711")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Injector/Injector/Startup.cs b/Injector/Injector/Startup.cs new file mode 100644 index 0000000..83165a3 --- /dev/null +++ b/Injector/Injector/Startup.cs @@ -0,0 +1,559 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Injector +{ + class Startup + { + private static string _firmwareImage; + private static string _outputFile; + private static FirmwareSection _section = FirmwareSection.None; + private static Dictionary _codeFiles; + private static Dictionary _rstFiles; + + internal enum Action + { + None, + GenerateHFile, + FindFreeBlock, + ApplyPatches + } + + internal enum ExitCode + { + Success = 0, + Failure = 1 + } + + static void Main(string[] args) + { + try + { + _codeFiles = new Dictionary(); + _rstFiles = new Dictionary(); + + //Assume success to start with + Environment.ExitCode = (int)ExitCode.Success; + + var action = Action.None; + + //Parse command line arguments + foreach (var arg in args) + { + var parts = arg.TrimStart(new char[] { '/' }).Split(new char[] { '=' }, + StringSplitOptions.RemoveEmptyEntries); + switch (parts[0].ToLower()) + { + case "action": + { + action = (Action)Enum.Parse(typeof(Action), parts[1]); + Console.WriteLine("Action: " + action.ToString()); + break; + } + case "section": + { + _section = (FirmwareSection)Enum.Parse(typeof(FirmwareSection), parts[1]); + Console.WriteLine("Section: " + _section.ToString()); + break; + } + case "firmware": + { + _firmwareImage = parts[1]; + Console.WriteLine("Firmware image: " + _firmwareImage); + _CheckFirmwareImage(); + break; + } + case "output": + { + _outputFile = parts[1]; + Console.WriteLine("Output file: " + _outputFile); + break; + } + default: + { + _ParseFileNames(ref _codeFiles, "code", parts[0], parts[1]); + _ParseFileNames(ref _rstFiles, "rst", parts[0], parts[1]); + break; + } + } + } + + //Firmware image file name is always required + if (string.IsNullOrEmpty(_firmwareImage)) + { + throw new ArgumentException("No/Invalid firmware image file name specified"); + } + + switch (action) + { + case Action.GenerateHFile: + { + if (string.IsNullOrEmpty(_outputFile)) + { + throw new ArgumentException("No/Invalid output file name specified"); + } + + Console.WriteLine("Generating .h file..."); + + _GenerateHFile(); + break; + } + case Action.ApplyPatches: + { + //Check required arguments for this action + + if (string.IsNullOrEmpty(_outputFile)) + { + throw new ArgumentException("No/Invalid output file name specified"); + } + + if (_codeFiles.Count == 0) + { + throw new ArgumentException("No code file name(s) specified"); + } + + if (_rstFiles.Count == 0) + { + throw new ArgumentException("No/Invalid RST file name specified"); + } + + Console.WriteLine("Applying patches..."); + _ApplyPatches(); + break; + } + case Action.FindFreeBlock: + { + //Check required arguments for this action + if (_section == FirmwareSection.None) + { + throw new ArgumentException("No/Invalid section specified"); + } + + Console.WriteLine("Retriving free space..."); + _GetFreeSpaceToFile(); + break; + } + default: + throw new ArgumentException("No/Invalid action specified"); + } + + Console.WriteLine("Done."); + } + catch (Exception ex) + { + //Uh-oh... + Environment.ExitCode = (int)ExitCode.Failure; + + var asm = System.Reflection.Assembly.GetExecutingAssembly(); + var asmName = asm.GetName(); + Console.WriteLine(asmName.Name + " v" + asmName.Version.ToString(3)); + Console.WriteLine("Actions:"); + Console.WriteLine("\tGenerateHFile\tGenerates C .h file of common XRAM & function equates."); + Console.WriteLine("\tFindFreeBlock\tWrites amount of free space for a section to file."); + Console.WriteLine("\tApplyPatches\tApplies available patches from code into firmware image."); + Console.WriteLine(); + Console.WriteLine("FATAL: " + ex.ToString()); + } + } + + private static void _CheckFirmwareImage() + { + var md5 = new MD5CryptoServiceProvider(); + var verified = new List(); + verified.Add("4C4C0001EC83102C4627D271FF8362A2"); + + var hash = BitConverter.ToString(md5.ComputeHash(File.ReadAllBytes(_firmwareImage))) + .Replace("-", string.Empty); + if (!verified.Contains(hash)) + { + Console.WriteLine("WARNING! This firmware version has not been " + + "verified to work with these patches."); + } + } + + private static void _ParseFileNames(ref Dictionary files, + string suffix, string name, string value) + { + if (name.ToLower().EndsWith(suffix)) + { + var section = FirmwareSection.Base; + int s; + + if (int.TryParse(name.Substring(0, name.Length - suffix.Length), out s)) + { + section = (FirmwareSection)s; + } + + files.Add(section, value); + Console.WriteLine(suffix + " " + section.ToString() + " file: " + value); + } + } + + private static Dictionary _GetAddressMap(string fileName) + { + //Read in RST file and its label<->address map + var addressMap = new Dictionary(); + var ret = new Dictionary(); + var rst = new StreamReader(fileName, ASCIIEncoding.ASCII); + + while (true) + { + var line = rst.ReadLine(); + if (line == null) + { + break; + } + + if (line.EndsWith(":")) + { + var parts = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var label = parts[parts.Length - 1].TrimEnd(':'); + var address = parts[0]; + + if (label.StartsWith("_")) + { + ret.Add(label, Convert.ToInt32(address, 16)); + } + } + } + + rst.Close(); + + return ret; + } + + private static void _GenerateHFile() + { + var stream = new StreamWriter(_outputFile); + + //Read in firmware image + var image = new FirmwareImage(_firmwareImage); + image.Open(); + + var pattern = new byte?[] { 0x90, 0xF0, 0xB8, 0xE0, //mov DPTR, #0xF0B8 \ movx a, @DPTR + 0x90, null, null, 0xF0, //mov DPTR, #0x???? \ movx @DPTR, a + 0x90, 0xF0, 0xB9, 0xE0 }; //mov DPTR, #0xF0B9 \ movx a, @DPTR \ movx DPTR, #0x???? + FirmwareSection section; + int address; + if (image.FindPattern(pattern, out section, out address)) + { + var a = image.GetSection(section)[address + 5] << 8; + a |= image.GetSection(section)[address + 6]; + + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1};", a.ToString("X04"), "bmRequestType")); + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1};", (a + 1).ToString("X04"), "bRequest")); + } + + pattern = new byte?[] { 0x90, null, null, 0xE0, //mov DPTR, #0x???? \ movx a, @DPTR + 0xB4, 0x28 }; //cjne A, #0x28, ???? + if (image.FindPattern(pattern, out section, out address)) + { + var a = image.GetSection(section)[address + 1] << 8; + a |= image.GetSection(section)[address + 2]; + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1}[16];", a.ToString("X04"), "scsi_cdb")); + + stream.WriteLine(string.Format("#define {0} 0x{1}", "DEFAULT_READ_SECTOR_HANDLER", (address + 7).ToString("X04"))); + pattern = new byte?[] { 0x90, (byte)((a >> 8) & 0xFF), (byte)(a & 0xFF), //mov DPTR, #scsi_tag + 0xE0, 0x12 }; //mvox A, @DPTR \ lcall 0x???? + if (image.FindPattern(pattern, address, out section, out address)) + { + stream.WriteLine(string.Format("#define {0} 0x{1}", "DEFAULT_CDB_HANDLER", address.ToString("X04"))); + } + } + + pattern = new byte?[] { 0x90, 0xF2, 0x1C, //mov DPTR, #0xF21C + 0x74, 0x55, 0xF0, //mov A, #0x55 \ movx @DPTR, A + 0x74, 0x53, 0xF0, //mov A, #0x53 \ movx @DPTR, A + 0x74, 0x42, 0xF0, //mov A, #0x42 \ movx @DPTR, A + 0x74, 0x53, 0xF0, //mov A, #0x53 \ movx @DPTR, A + 0x90 }; //mov DPTR, #0x???? + if (image.FindPattern(pattern, out section, out address)) + { + var a = image.GetSection(section)[address + pattern.Length] << 8; + a |= image.GetSection(section)[address + pattern.Length + 1]; + + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1}[4];", (a - 3).ToString("X04"), "scsi_tag")); + } + + pattern = new byte?[] { 0xC0, 0xE0, 0xC0, 0x83, 0xC0, 0x82, //push ACC \ push DPH \ push DPL + 0x90, 0xF0, 0x20, 0xE0, //mov DPTR, #0xF020 \ movx A, @DPTR + 0x30, 0xE1, null, //jnb ACC.1, ???? + 0x12, null, null, 0x90 }; //lcall ???? \ mov DPTR, #0x???? + if (image.FindPattern(pattern, out section, out address)) + { + var a = image.GetSection(section)[address + 17] << 8; + a |= image.GetSection(section)[address + 18]; + + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1};", a.ToString("X04"), "FW_EPIRQ")); + } + + stream.WriteLine(string.Format("__xdata __at 0x{0} BYTE {1}[1024];", "B000", "EPBUF")); + + stream.Close(); + } + + private static void _GetFreeSpaceToFile() + { + //Read in firmware image + var image = new FirmwareImage(_firmwareImage); + image.Open(); + + File.WriteAllText(_outputFile, "0x" + image.FindLastFreeChunk(_section).ToString("X04")); + } + + private static void _ApplyPatches() + { + //Read in firmware image + var image = new FirmwareImage(_firmwareImage); + image.Open(); + + //Read in the RST files + var maps = new Dictionary>(); + foreach (var file in _rstFiles) + { + maps.Add(file.Key, _GetAddressMap(file.Value)); + } + + //Find how much free space is left on each page + var emptyStart = new Dictionary(); + for (FirmwareSection i = FirmwareSection.Base; i < FirmwareSection.SectionF; i++) + { + emptyStart.Add(i, image.FindLastFreeChunk(i)); + } + + //Embed our code files into the firmware image + foreach (var file in _codeFiles) + { + var code = File.ReadAllBytes(file.Value); + Array.Copy(code, 0, image.GetSection(file.Key), emptyStart[file.Key], code.Length); + emptyStart[file.Key] += code.Length; + } + + //Find the off-page call stubs + var stubs = new Dictionary(); + int saddr = 0; + var spattern = new byte?[] { 0xC0, 0x5B, 0x74, 0x08, //push RAM_5B \ mov A, #8 + 0xC0, 0xE0, 0xC0, 0x82, 0xC0, 0x83, //push ACC \ push DPL \ push DPH + 0x75, 0x5B }; //mov RAM_5B, #0x?? + FirmwareSection fs; + for (FirmwareSection i = FirmwareSection.Section0; i <= FirmwareSection.SectionF; i++) + { + if (image.FindPattern(spattern, saddr, out fs, out saddr)) + { + stubs.Add(i, saddr); + saddr += spattern.Length; //move ahead so we can find the next stub + } + } + + //Hook into control request handling + foreach (var map in maps) + { + if (map.Value.ContainsKey("_HandleControlRequest")) + { + var address = map.Value["_HandleControlRequest"]; + var pattern = new byte?[] { 0x12, null, null, //lcall #0x???? + 0x90, 0xFE, 0x82, 0xE0, //mov DPTR, #0xFE82 \ movx A, @DPTR + 0x54, 0xEF, 0xF0 }; //anl A, #0xEF \ movx @DPTR, A + FirmwareSection s; + int a; + if (image.FindPattern(pattern, out s, out a)) + { + a = (image.GetSection(s)[a + 1] << 8) | image.GetSection(s)[a + 2]; + + image.GetSection(s)[a + 1] = (byte)((address >> 8) & 0xFF); + image.GetSection(s)[a + 2] = (byte)(address & 0xFF); + if (map.Key != FirmwareSection.Base) + { + image.GetSection(s)[a + 4] = (byte)((stubs[map.Key] >> 8) & 0xFF); + image.GetSection(s)[a + 5] = (byte)(stubs[map.Key] & 0xFF); + } + } + break; + } + } + + //Replace the EP interrupt vector, handling all incoming and outgoing non-control data + foreach (var map in maps) + { + //This part must be on the base page + if (map.Value.ContainsKey("_EndpointInterrupt")) + { + var address = map.Value["_EndpointInterrupt"]; + var s = image.GetSection(FirmwareSection.Base); + s[0x0014] = (byte)((address >> 8) & 0xFF); + s[0x0015] = (byte)(address & 0xFF); + } + + if (map.Value.ContainsKey("_HandleEndpointInterrupt")) + { + //Find the base page location to patch + var pattern = new byte?[] { 0x30, 0xE1, null, //jnb ACC.1, #0x???? + 0x12, null, null, //lcall #0x???? + 0x90, 0xFE, 0x82, 0xE0, //mov DPTR, #0xFE82 \ movx A, @DPTR + 0x54, 0xEF, 0xF0 }; //anl A, #0xEF \ movx @DPTR, A + FirmwareSection ps; + int pa; + if (image.FindPattern(pattern, out ps, out pa)) + { + //Create off-page stub for this if necessary + var address = map.Value["_HandleEndpointInterrupt"]; + var stubAddress = address; + if (map.Key != FirmwareSection.Base) + { + stubAddress = emptyStart[FirmwareSection.Base]; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x90; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((address >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(address & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x02; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((stubs[map.Key] >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(stubs[map.Key] & 0xFF); + } + + //Apply the patch + var s = image.GetSection(ps); + s[pa + 0] = 0x60; + s[pa + 1] = 0x0B; + s[pa + 2] = 0x00; + s[pa + 4] = (byte)((stubAddress >> 8) & 0xFF); + s[pa + 5] = (byte)(stubAddress & 0xFF); + for (int i = 0; i < 7; i++) + { + s[pa + 6 + i] = 0x00; + } + } + } + } + + //Apply CDB-handling code + foreach (var map in maps) + { + if (map.Value.ContainsKey("_HandleCDB")) + { + var pattern = new byte?[] { 0x90, null, null, 0xE0, //mov DPTR, #0x???? \ movx a, @DPTR + 0xB4, 0x28 }; //cjne A, #0x28, ???? + FirmwareSection ps; + int pa; + if (image.FindPattern(pattern, out ps, out pa)) + { + //Create off-page stub for this if necessary + var address = map.Value["_HandleCDB"]; + var stubAddress = address; + if (map.Key != FirmwareSection.Base) + { + stubAddress = emptyStart[FirmwareSection.Base]; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x90; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((address >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(address & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x02; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((stubs[map.Key] >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(stubs[map.Key] & 0xFF); + } + + //Apply the patch + var s = image.GetSection(FirmwareSection.Base); + s[pa + 0] = 0x02; + s[pa + 1] = (byte)((stubAddress >> 8) & 0xFF); + s[pa + 2] = (byte)(stubAddress & 0xFF); + } + } + } + + //Add our own code to the infinite loop + foreach (var map in maps) + { + if (map.Value.ContainsKey("_LoopDo")) + { + var pattern = new byte?[] { 0x90, null, null, 0xE0, //mov DPTR, #0x???? \ movx A, @DPTR + 0xB4, 0x01, null, //cjne A, #1, #0x???? + 0x90, 0xF0, 0x79 }; //mov DPTR, #0xF079 + FirmwareSection ps; + int pa; + if (image.FindPattern(pattern, out ps, out pa)) + { + //Create off-page stub for this if necessary + var address = map.Value["_LoopDo"]; + var stubAddress = address; + if (map.Key != FirmwareSection.Base) + { + stubAddress = emptyStart[FirmwareSection.Base]; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x90; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((address >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(address & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x02; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((stubs[map.Key] >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(stubs[map.Key] & 0xFF); + } + + var s = image.GetSection(ps); + var loopDoStart = emptyStart[FirmwareSection.Base]; + s[emptyStart[FirmwareSection.Base]++] = 0x12; + s[emptyStart[FirmwareSection.Base]++] = (byte)((stubAddress >> 8) & 0xFF); + s[emptyStart[FirmwareSection.Base]++] = (byte)(stubAddress & 0xFF); + s[emptyStart[FirmwareSection.Base]++] = 0x90; + s[emptyStart[FirmwareSection.Base]++] = image.GetSection(ps)[pa + 1]; + s[emptyStart[FirmwareSection.Base]++] = image.GetSection(ps)[pa + 2]; + s[emptyStart[FirmwareSection.Base]++] = 0x22; + s[pa + 0] = 0x12; + s[pa + 1] = (byte)((loopDoStart >> 8) & 0xFF); + s[pa + 2] = (byte)(loopDoStart & 0xFF); + } + } + } + + //Apply password patch code + foreach (var map in maps) + { + if (map.Value.ContainsKey("_PasswordReceived")) + { + var pattern = new byte?[] { 0x90, 0xF2, 0x4C, 0xF0, 0xA3, //mov DPTR, #0xF24C \ movx @DPTR, A \ inc DPTR + 0xC0, 0x83, 0xC0, 0x82, 0x12, //push DPH \ push DPL + null, null, 0xD0, 0x82, 0xD0, 0x83, 0xF0, //lcall #0x???? \ pop DPL \ pop DPH \ movx @DPTR, A + 0x90, 0xF2, 0x53, 0x74, 0x80, 0xF0, //mov DPTR, #0xF253 \ mov A, #0x80 \ movx @DPTR, A + 0x90, 0xF2, 0x53, 0xE0, //mov DPTR, #0xF253 \ movx A, @DPTR + 0x30, 0xE7, null, //jnb ACC.7, #0x???? + 0x12, null, null, 0x40, null, //lcall #0x???? \ jc #0x???? + 0x12, null, null, 0x7F, 0x00, 0x22 }; //lcall #0x???? \ mov R7, #0 \ ret + FirmwareSection ps; + int pa; + if (image.FindPattern(pattern, out ps, out pa)) + { + //Create off-page stub for this if necessary + var address = map.Value["_PasswordReceived"]; + var stubAddress = address; + if (map.Key != FirmwareSection.Base) + { + stubAddress = emptyStart[FirmwareSection.Base]; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x90; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((address >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(address & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = 0x02; + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)((stubs[map.Key] >> 8) & 0xFF); + image.GetSection(FirmwareSection.Base)[emptyStart[FirmwareSection.Base]++] = (byte)(stubs[map.Key] & 0xFF); + } + + //Apply the patch + pa += 0x24; + var passRecvdStart = emptyStart[ps] + (ps == FirmwareSection.Base ? 0x0000 : 0x5000); + image.GetSection(ps)[emptyStart[ps]++] = 0x12; + image.GetSection(ps)[emptyStart[ps]++] = image.GetSection(ps)[pa + 0]; + image.GetSection(ps)[emptyStart[ps]++] = image.GetSection(ps)[pa + 1]; + image.GetSection(ps)[emptyStart[ps]++] = 0x02; + image.GetSection(ps)[emptyStart[ps]++] = (byte)((stubAddress >> 8) & 0xFF); + image.GetSection(ps)[emptyStart[ps]++] = (byte)(stubAddress & 0xFF); + image.GetSection(ps)[pa + 0] = (byte)((passRecvdStart >> 8) & 0xFF); + image.GetSection(ps)[pa + 1] = (byte)(passRecvdStart & 0xFF); + } + } + } + + //Write the resulting file out + image.Save(_outputFile); + } + } +} diff --git a/docs/PinsToShortUponPlugInForBootMode.jpg b/docs/PinsToShortUponPlugInForBootMode.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebb10c6356fca49358599d2e6f1e291f1451c54f GIT binary patch literal 113825 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 literal 0 HcmV?d00001 diff --git a/firmware/build.bat b/firmware/build.bat new file mode 100644 index 0000000..b42cc93 --- /dev/null +++ b/firmware/build.bat @@ -0,0 +1,41 @@ +@ECHO OFF + +REM Set things up and create bin directory if necessary. +SETLOCAL ENABLEDELAYEDEXPANSION +SET BUILD_FILES= +IF NOT EXIST bin\NUL MKDIR bin + +REM Build each file in the list. +FOR %%A IN ( +main +timers +usb +control +scsi +) DO ( +ECHO *** Building %%A.c... +sdcc --model-small -mmcs51 -pdefcpu -c -obin\%%A.rel %%A.c +IF ERRORLEVEL 1 GOTO ERRORS +SET "BUILD_FILES=!BUILD_FILES! bin\%%A.rel" +) + +REM Build Intel Hex and BIN versions of combined file. +sdcc --xram-loc 0x6000 -o bin\output.hex %BUILD_FILES% +IF ERRORLEVEL 1 GOTO ERRORS +makebin -p bin\output.hex bin\output.bin + +REM Create firmware and burner images from templates. +copy /y ..\templates\FWdummy.bin bin\fw.bin > NUL +copy /y ..\templates\BNdummy.bin bin\bn.bin > NUL +..\tools\sfk partcopy bin\output.bin -fromto 0 -1 bin\fw.bin 512 -yes > NUL +..\tools\sfk partcopy bin\output.bin -fromto 0 -1 bin\bn.bin 512 -yes > NUL + +GOTO END + +:ERRORS +ECHO *** There were errors^^! *** + +:END +ECHO *** Done. + +ENDLOCAL diff --git a/firmware/control.c b/firmware/control.c new file mode 100644 index 0000000..755e710 --- /dev/null +++ b/firmware/control.c @@ -0,0 +1,184 @@ +#include "defs.h" +#include "usb.h" +#include "timers.h" + +static const BYTE deviceDescriptor[] = { 0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, + 0xFE, 0x13, 0x01, 0x52, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01 }; +static const BYTE configDescriptor[] = { 0x09, 0x02, sizeof(configDescriptor) & 0xFF, sizeof(configDescriptor) >> 8, 0x02, 0x01, 0x00, 0x80, 0x4B, + 0x09, 0x04, 0x00, 0x00, 0x03, 0x08, 0x06, 0x50, 0x00, + 0x07, 0x05, 0x81, 0x02, 0x40, 0x00, 0x00, + 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00, + 0x07, 0x05, 0x83, 0x03, 0x08, 0x00, 0x00, + 0x09, 0x04, 0x01, 0x00, 0x02, 0x03, 0x01, 0x01, 0x00, + 0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, + sizeof(HIDreportDescriptor) & 0xFF, + sizeof(HIDreportDescriptor) >> 8, + 0x07, 0x05, 0x83, 0x03, 0x08, 0x00, 0x01, + //This is a dummy endpoint to make the descriptor != 0x40, because the controller is stupid. + 0x07, 0x05, 0x04, 0x03, 0x08, 0x00, 0x01 }; +static const BYTE HIDreportDescriptor[] = { 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, + 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, + 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, + 0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, + 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x95, 0x06, + 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, + 0x29, 0x65, 0x81, 0x00, 0xC0 }; +static const BYTE deviceQualifierDescriptor[] = { 0x0A, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00 }; + +void EP0ACK() +{ + EP0CS = bmEP0ACK; +} + +static BYTE SetAddress() +{ + BYTE ret = FALSE; + + if (wValue < 0x7F) + { + EP0ACK(); + ret = TRUE; + } + + return ret; +} + +static BYTE GetDescriptor() +{ + BYTE type = (wValue >> 8) & 0xFF; + BYTE i, total; + BYTE ret = FALSE; + + switch (type) + { + case 0x01: + { + for (i = 0; i < 0x12; i++) + { + EP0.fifo = deviceDescriptor[i]; + } + + SendControlResponse(wLength < 0x12 ? wLength : 0x12); + ret = TRUE; + + break; + } + case 0x02: + { + total = wLength < sizeof(configDescriptor) ? wLength : sizeof(configDescriptor); + for (i = 0; i < total; i++) + { + EP0.fifo = configDescriptor[i]; + } + + SendControlResponse(total); + ret = TRUE; + + break; + } + case 0x06: + { + for (i = 0; i < sizeof(deviceQualifierDescriptor); i++) + { + EP0.fifo = deviceQualifierDescriptor[i]; + } + + SendControlResponse(wLength < sizeof(deviceQualifierDescriptor) ? wLength : sizeof(deviceQualifierDescriptor)); + ret = TRUE; + + break; + } + case 0x22: + { + for (i = 0; i < sizeof(HIDreportDescriptor); i++) + { + EP0.fifo = HIDreportDescriptor[i]; + } + + SendControlResponse(wLength < sizeof(HIDreportDescriptor) ? wLength : sizeof(HIDreportDescriptor)); + ret = TRUE; + + break; + } + default: + { + break; + } + } + + return ret; +} + +static BYTE SetConfiguration() +{ + BYTE ret = FALSE; + + if (wValue <= 1) + { + EP0ACK(); + ret = TRUE; + } + + return ret; +} + +BYTE HandleStandardRequest() +{ + switch(bRequest) + { + case 0x05: + { + return SetAddress(); + } + case 0x06: + { + return GetDescriptor(); + } + case 0x09: + { + return SetConfiguration(); + } + default: + { + return FALSE; + } + } +} + +static BYTE GetMaxLUN() +{ + EP0.fifo = 0x00; + SendControlResponse(wLength < 0x01 ? wLength : 0x01); + + return TRUE; +} + +BYTE HandleClassRequest() +{ + switch(bRequest) + { + case 0x09: + { + EP0CS = 0x05; + return TRUE; + } + case 0x0A: + { + EP0ACK(); + return TRUE; + } + case 0xFE: + { + return GetMaxLUN(); + } + default: + { + return FALSE; + } + } +} + +BYTE HandleVendorRequest() +{ + return FALSE; +} diff --git a/firmware/defs.h b/firmware/defs.h new file mode 100644 index 0000000..0568382 --- /dev/null +++ b/firmware/defs.h @@ -0,0 +1,202 @@ +#ifndef DEFS_H +#define DEFS_H + +#define MSB(word) (BYTE)(((WORD)(word) >> 8) & 0xff) +#define LSB(word) (BYTE)((WORD)(word) & 0xff) + +#define XVAL(addr) (*( __xdata volatile unsigned char *)(addr)) +#define IVAL(addr) (*( __idata volatile unsigned char *)(addr)) + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +#define TRUE 1 +#define FALSE 0 + +#define BANK0_PA 0x008000UL +#define BANK1_VA 0x4000U +#define BANK1_PA 0x00C000UL +#define BANK2_VA 0x6000U +#define BANK2_PA 0x00E000UL + +#define usb_buffer_PA 0x008000UL +#define usb_buffer_VA 0x0000U + +#define USB_VECT 0 +#define TMR0_VECT 1 +#define EP_VECT 2 +#define TMR1_VECT 3 +#define COM0_VECT 4 + +#define bmAttach 0x80 +#define bmSpeed 7 +#define bmSuperSpeed 4 +#define bmHighSpeed 0 +#define bmFullSpeed 1 +#define bmSpeedChange 0x80 +#define bmEP2IRQ 2 +#define bmEP4IRQ 8 +#define bmEP0ACK 1 +#define bmEP0NAK 2 +#define bmEP0IN 4 +#define bmEP0STALL 8 +#define bmSUDAV 0x80 +#define bmSTALL 2 + +#define bmNandReady 1 + +#define bmNandDma0 0 +#define bmNandDma1 0x80 +#define bmNandDmaRead 0 +#define bmNandDmaWrite 0x40 + +#define bmDmaCmd 7 +#define bmDmaCopy 2 +#define bmDmaFill 4 +#define bmDmaWidth8 0 +#define bmDmaWidth16 0x40 +#define bmDmaWidth32 0x80 + +#define bmPRAM 1 + +__sfr __at (0x80) P0 ; +__sfr __at (0x90) P1 ; +__sfr __at (0xA0) P2 ; +__sfr __at (0xB0) P3 ; +__sfr __at (0xD0) PSW ; +__sfr __at (0xE0) ACC ; +__sfr __at (0xF0) B ; +__sfr __at (0x81) SP ; +__sfr __at (0x82) DPL ; +__sfr __at (0x83) DPH ; +__sfr __at (0x87) PCON; +__sfr __at (0x88) TCON; +__sfr __at (0x89) TMOD; +__sfr __at (0x8A) TL0 ; +__sfr __at (0x8B) TL1 ; +__sfr __at (0x8C) TH0 ; +__sfr __at (0x8D) TH1 ; +__sfr __at (0xA8) IE ; +__sfr __at (0xB8) IP ; +__sfr __at (0x98) SCON; +__sfr __at (0x99) SBUF; + +/* BIT Register */ +/* PSW */ +__sbit __at (0xD7) CY ; +__sbit __at (0xD6) AC ; +__sbit __at (0xD5) F0 ; +__sbit __at (0xD4) RS1 ; +__sbit __at (0xD3) RS0 ; +__sbit __at (0xD2) OV ; +__sbit __at (0xD0) P ; + +/* TCON */ +__sbit __at (0x8F) TF1 ; +__sbit __at (0x8E) TR1 ; +__sbit __at (0x8D) TF0 ; +__sbit __at (0x8C) TR0 ; +__sbit __at (0x8B) IE1 ; +__sbit __at (0x8A) IT1 ; +__sbit __at (0x89) IE0 ; +__sbit __at (0x88) IT0 ; + +/* IE */ +__sbit __at (0xAF) EA ; +__sbit __at (0xAC) ES ; +__sbit __at (0xAB) ET1 ; +__sbit __at (0xAA) EX1 ; +__sbit __at (0xA9) ET0 ; +__sbit __at (0xA8) EX0 ; + +/* IP */ +__sbit __at (0xBC) PS ; +__sbit __at (0xBB) PT1 ; +__sbit __at (0xBA) PX1 ; +__sbit __at (0xB9) PT0 ; +__sbit __at (0xB8) PX0 ; + +/* P3 */ +__sbit __at (0xB7) RD ; +__sbit __at (0xB6) WR ; +__sbit __at (0xB5) T1 ; +__sbit __at (0xB4) T0 ; +__sbit __at (0xB3) INT1; +__sbit __at (0xB2) INT0; +__sbit __at (0xB1) TXD ; +__sbit __at (0xB0) RXD ; + +/* SCON */ +__sbit __at (0x9F) SM0 ; +__sbit __at (0x9E) SM1 ; +__sbit __at (0x9D) SM2 ; +__sbit __at (0x9C) REN ; +__sbit __at (0x9B) TB8 ; +__sbit __at (0x9A) RB8 ; +__sbit __at (0x99) TI ; +__sbit __at (0x98) RI ; + +__xdata __at 0xF000 volatile BYTE REGBANK; +__xdata __at 0xF008 volatile BYTE USBCTL; +__xdata __at 0xF009 volatile BYTE USBSTAT; +__xdata __at 0xF027 volatile BYTE USBIRQ; +__xdata __at 0xF020 volatile BYTE EPIRQ; +__xdata __at 0xF030 volatile BYTE EPIE; +__xdata __at 0xF048 volatile BYTE EP0CS; +__xdata __at 0xF0B8 volatile BYTE SETUPDAT[8]; + +typedef struct +{ + BYTE r0, r1, r2, r3, r4; + BYTE ptr_l, ptr_m, ptr_h; + BYTE r8, r9; + BYTE offset; + BYTE rB; + BYTE len_l, len_m, len_h; + BYTE rF, r10, r11, r12; + BYTE cs; + BYTE r14, r15, r16, r17, r18, r19; + BYTE fifo_count; + BYTE r1B; + BYTE fifo; +} EPREGS; + +__xdata __at 0xF1C0 volatile EPREGS EP0; +__xdata __at 0xF200 volatile EPREGS EP1; +__xdata __at 0xF240 volatile EPREGS EP2; +__xdata __at 0xF280 volatile EPREGS EP3; +__xdata __at 0xF2C0 volatile EPREGS EP4; + +__xdata __at 0xF608 volatile BYTE NANDCSOUT; +__xdata __at 0xF618 volatile BYTE NANDCSDIR; + +__xdata __at 0xF900 volatile BYTE DMASRCL; +__xdata __at 0xF901 volatile BYTE DMASRCM; +__xdata __at 0xF902 volatile BYTE DMASRCH; +__xdata __at 0xF904 volatile BYTE DMADSTL; +__xdata __at 0xF905 volatile BYTE DMADSTM; +__xdata __at 0xF906 volatile BYTE DMADSTH; +__xdata __at 0xF908 volatile BYTE DMASIZEL; +__xdata __at 0xF909 volatile BYTE DMASIZEM; +__xdata __at 0xF90A volatile BYTE DMASIZEH; +__xdata __at 0xF90C volatile BYTE DMAFILL0; +__xdata __at 0xF90D volatile BYTE DMAFILL1; +__xdata __at 0xF90E volatile BYTE DMAFILL2; +__xdata __at 0xF90F volatile BYTE DMAFILL3; +__xdata __at 0xF930 volatile BYTE DMACMD; + +__xdata __at 0xFA14 volatile BYTE GPIO0DIR; +__xdata __at 0xFA15 volatile BYTE GPIO0OUT; +__xdata __at 0xFA38 volatile BYTE WARMSTATUS; + +__xdata __at 0xFA40 volatile BYTE BANK0PAL; +__xdata __at 0xFA41 volatile BYTE BANK0PAH; +__xdata __at 0xFA42 volatile BYTE BANK1VA; +__xdata __at 0xFA43 volatile BYTE BANK1PAL; +__xdata __at 0xFA44 volatile BYTE BANK1PAH; +__xdata __at 0xFA45 volatile BYTE BANK2VA; +__xdata __at 0xFA46 volatile BYTE BANK2PAL; +__xdata __at 0xFA47 volatile BYTE BANK2PAH; +__xdata __at 0xFA48 volatile BYTE PRAMCTL; //bit 0 set means run from PRAM + +#endif diff --git a/firmware/main.c b/firmware/main.c new file mode 100644 index 0000000..5ae362e --- /dev/null +++ b/firmware/main.c @@ -0,0 +1,170 @@ +#include "defs.h" +#include "timers.h" +#include "usb.h" + +extern void usb_isr(void) __interrupt USB_VECT; +extern void ep_isr(void) __interrupt EP_VECT; +extern void tmr0isr(void) __interrupt TMR0_VECT; +extern void tmr1isr(void) __interrupt TMR1_VECT; + +#define KEY_DELAY 8192 +#define KEY_BUFFER_SIZE 0x2000 +static const BYTE keyData[KEY_BUFFER_SIZE] = { /*0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, + 0x15, 0x08, 0x00, 0xFF, 0x00, 0xF5, 0x11, 0x00, 0x12, 0x00, 0x17, 0x00, 0x08, 0x00, 0x13, + 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0xFF, 0x00, 0xF5, 0x28, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xF0, 0x0B, 0x02, 0x08, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x2C, 0x00, 0x1A, + 0x02, 0x12, 0x00, 0x15, 0x00, 0x0F, 0x00, 0x07, 0x00, 0x1E, 0x02, 0x1E, 0x02, 0x1E, 0x02, + 0x28, 0x00*/ 0x12, 0x34, 0x56, 0x78 }; +int key_index = 0; +volatile BYTE send_keys_enabled = 0; +DWORD wait_counter = KEY_DELAY; +DWORD wait_tick; + +void InitHardware() +{ + //Set up RAM mapping just beyond our own code + BANK0PAL = BANK0_PA>>9; + BANK0PAH = BANK0_PA>>17; + BANK1VA = BANK1_VA>>8; + BANK1PAL = BANK1_PA>>9; + BANK1PAH = BANK1_PA>>17; + BANK2VA = BANK2_VA>>8; + BANK2PAL = BANK2_PA>>9; + BANK2PAH = BANK2_PA>>17; + + XVAL(0xF809) = 7; + XVAL(0xF80A) = 0x1F; + XVAL(0xF810) = 0x60; + XVAL(0xF811) = 0; + XVAL(0xF08F) = 0; + + XVAL(0xFA6F) = 0x1F; + XVAL(0xFA60) = 2; + XVAL(0xFA61) = 0; + XVAL(0xFA64) = 0; + XVAL(0xFA65) = 0; + XVAL(0xFA66) = 0; + XVAL(0xFA67) = 0; + XVAL(0xFA62) = 0x0F; + XVAL(0xFA6F) = 0x1F; + + GPIO0DIR &= 0xFD; + GPIO0OUT |= 2; + + XVAL(0xFA21) = 7; + XVAL(0xFA21) &= 0xFB; + + XVAL(0xFA68) &= 0xF7; + XVAL(0xFA69) &= 0xF7; + XVAL(0xFA6A) &= 0xF7; + XVAL(0xFA6B) &= 0xF7; + + XVAL(0xFE00) = 0; + XVAL(0xFE00) = 0x80; + + XVAL(0xFA50) = 0x20; + + XVAL(0xFE01) = 0; + XVAL(0xFE02) = 0x45; + + TMOD = 0x11; + TH0 = 0xF0; + TL0 = 0x5F; + TH1 = 0xF0; + TL1 = 0x5F; + IP = 1; + TCON = 0x10; + SCON = 0; + IE = 0x80; +} + +void DoUSBRelatedInit() +{ + if (WARMSTATUS & 2) + { + return; + } + + REGBANK = 5; + XVAL(0xF210) = 0xFF; + XVAL(0xF211) = 2; + XVAL(0xF212) = 3; + XVAL(0xF213) = 0x24; + REGBANK = 0; + XVAL(0xFA6B) = 0xFF; + while((XVAL(0xF014) & 3)==0); +} + +void SendKey(BYTE code, BYTE modifiers) +{ + int i; + + EP3.cs = 0; + while (EP3.cs & 0x40); + + EP3.fifo = modifiers; + EP3.fifo = 0; + EP3.fifo = code; + for (i = 0; i < 5; i++) + { + EP3.fifo = 0; + } + + EP3.len_l = 8; + EP3.len_m = 0; + EP3.len_h = 0; + EP3.cs = 0x40; +} + +void main() +{ + InitHardware(); + DoUSBRelatedInit(); + InitUSB(); + InitTicks(); + InitLED(); + LEDBlink(); + + while (1) + { + HandleUSBEvents(); + + if (wait_tick++ >= KEY_DELAY) + { + if (wait_counter < KEY_DELAY) + { + wait_counter++; + } + } + + if (send_keys_enabled && wait_counter >= KEY_DELAY) + { + if (keyData[key_index]) + { + //Send this key, with some padding before, since something's wonky with endpoint 3 + SendKey(0x00, 0x00); + SendKey(0x00, 0x00); + SendKey(0x00, 0x00); + SendKey(0x00, 0x00); + SendKey(keyData[key_index], keyData[key_index + 1]); + SendKey(0x00, 0x00); + } + else + { + //Wait a while + wait_counter = 0; + wait_tick = 0; + } + + //Move to next key + key_index += 2; + + //Are we done? + if (key_index >= sizeof(keyData)) + { + send_keys_enabled = 0; + } + } + } +} diff --git a/firmware/scsi.c b/firmware/scsi.c new file mode 100644 index 0000000..7673c46 --- /dev/null +++ b/firmware/scsi.c @@ -0,0 +1,158 @@ +#include "defs.h" +#include "string.h" +#include "usb.h" + +#define PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define TEST_UNIT_READY 0x00 +#define INQUIRY 0x12 +#define READ_FORMAT_CAPACITIES 0x23 +#define MODE_SENSE 0x1A +#define REQUEST_SENSE 0x03 + +#define VENDOR_BOOT 0xBF +#define VENDOR_INFO 0x05 +#define CUSTOM_XPEEK 0x06 +#define CUSTOM_XPOKE 0x07 +#define CUSTOM_IPEEK 0x08 +#define CUSTOM_IPOKE 0x09 + +BYTE scsi_status; +DWORD scsi_data_residue; +DWORD scsi_transfer_size; +BYTE scsi_tag[4]; +BYTE scsi_dir_in; +BYTE scsi_lun; +BYTE scsi_cdb[16]; +BYTE scsi_cdb_size; + +BYTE HandleCDB() +{ + //Default to returning a bad status + scsi_status = 1; + + switch(scsi_cdb[0]) + { + case PREVENT_ALLOW_MEDIUM_REMOVAL: + { + scsi_status = 0; + return 1; + } + case TEST_UNIT_READY: + { + return 1; + } + case INQUIRY: + { + memset(usb_buffer, 0, 36); + usb_buffer[1] = 0x80; //removable media + usb_buffer[3] = 0x01; //because the UFI spec says so + usb_buffer[4] = 0x1F; //additional length + SendData1(36, 0); + scsi_status = 0; + return 1; + } + case READ_FORMAT_CAPACITIES: + { + memset(usb_buffer, 0, 12); + usb_buffer[3] = 0x08; //capacity list length + usb_buffer[6] = 0x10; //number of blocks (sectors) (dummy 2MB) + usb_buffer[8] = 0x03; + usb_buffer[10] = 0x02; //block length (512 bytes/sector) + SendData1(12, 0); + scsi_status = 0; + return 1; + } + case MODE_SENSE: + { + memset(usb_buffer, 0, 8); + usb_buffer[0] = 0x03; + usb_buffer[2] = 0x80; + SendData1(4, 0); + scsi_status = 0; + return 1; + } + case REQUEST_SENSE: + { + memset(usb_buffer, 0, 18); + usb_buffer[0] = 0x70; + usb_buffer[2] = 0x02; + usb_buffer[7] = 10; + usb_buffer[12] = 0x3A; + SendData1(18, 0); + scsi_status = 0; + return 1; + } + //Vendor-specific requests + case 0x06: + case 0xC6: + case 0xC7: + { + switch(scsi_cdb[1]) + { + case CUSTOM_XPEEK: + { + usb_buffer[0] = XVAL((scsi_cdb[2] << 8) | scsi_cdb[3]); + SendData1(1, 0); + break; + } + case CUSTOM_XPOKE: + { + XVAL((scsi_cdb[2] << 8) | scsi_cdb[3]) = scsi_cdb[4]; + SendData1(1, 0); + break; + } + case CUSTOM_IPEEK: + { + usb_buffer[0] = IVAL(scsi_cdb[2]); + SendData1(1, 0); + break; + } + case CUSTOM_IPOKE: + { + IVAL(scsi_cdb[2]) = scsi_cdb[3]; + SendData1(1, 0); + break; + } + case VENDOR_INFO: //get info + { + int i; + + memset(usb_buffer, 0x00, 0x210); + for (i = 0; i < 0x200; i++) + { + usb_buffer[i] = *((BYTE __xdata *)(0x5000 + i)); + } + + usb_buffer[0x200] = 'I'; + usb_buffer[0x201] = 'F'; + SendData1(0x210, 0); + scsi_status = 0; + return 1; + } + case VENDOR_BOOT: + { + //This transfers control to boot mode and will not return. + XVAL(0xFA14) = 0x07; + XVAL(0xF747) &= 0xEF; + XVAL(0xFA15) = 0x06; + XVAL(0xFA38) |= 0x01; + XVAL(0xF08F) = 0x00; + XVAL(0xFA68) &= 0xF7; + XVAL(0xFA6A) &= 0xF7; + XVAL(0xFA48) &= 0xFE; + break; + } + default: + { + //Not handling it, then + return 0; + } + } + } + default: + { + //Not handling it, then + return 0; + } + } +} diff --git a/firmware/test.bat b/firmware/test.bat new file mode 100644 index 0000000..f2f1278 --- /dev/null +++ b/firmware/test.bat @@ -0,0 +1,2 @@ +@ECHO OFF +..\tools\DriveCom /action=SendFirmware /drive=E /burner=..\BINs\BN03V104M.BIN /firmware=bin\fw.bin diff --git a/firmware/timers.c b/firmware/timers.c new file mode 100644 index 0000000..39036cb --- /dev/null +++ b/firmware/timers.c @@ -0,0 +1,116 @@ +#include "defs.h" +#include "timers.h" + +static BYTE tmr0count, led_ticks, led_timer, led_tick_threshold; +static BYTE tmr1count; +static WORD tmr1reload; + +void tmr1isr(void) __interrupt TMR1_VECT +{ + TR1 = 0; + TH1 = MSB(tmr1reload); + TL1 = LSB(tmr1reload); + tmr1count++; + TR1 = 1; +} + +void InitTicks() +{ + if (XVAL(0xFA60) == 0x0F) + { + tmr1reload = 0xF63C; + } + else + { + tmr1reload = 0-(2500/(XVAL(0xFA60)+2)); + } + + tmr1count = 0; + TR1 = 0; + ET1 = 1; + TMOD = TMOD & 0x0F | 0x10; +} + +BYTE GetTickCount(void) +{ + return tmr1count; +} + +void tmr0isr(void) __interrupt TMR0_VECT +{ + //approx. 10 times per second + TR0 = 0; + TL0 = 0xE6; + TH0 = 0x96; + TR0 = 1; + + if ((GPIO0OUT & 2) == 0) //turned off + { + return; + } + + tmr0count++; + led_ticks++; + if (led_ticks < led_tick_threshold) + { + return; + } + + led_ticks = 0; + if (led_timer >= 31) + { + GPIO0OUT = 1; + led_timer = 0; + return; + } + + if (led_timer >= 10) + { + GPIO0OUT = ~GPIO0OUT; + led_timer++; + return; + } + + if (led_timer == 0) + { + return; + } + + if (GPIO0OUT & 1) + { + GPIO0OUT &= 0xFE; + } + else + { + GPIO0OUT |= 1; + } +} + +void SetLEDThreshold(int threshold) +{ + led_tick_threshold = threshold; +} + +void InitLED(void) +{ + led_tick_threshold = 100; + tmr0count = 0; + GPIO0OUT = 3; + led_ticks = 0; + led_timer = 0; + EA = 1; + ET0 = 1; + TR0 = 1; +} + +void LEDBlink(void) +{ + GPIO0OUT = 2; + led_timer = 1; +} + +void LEDOff(void) +{ + GPIO0OUT = 3; + led_timer = 0; +} diff --git a/firmware/timers.h b/firmware/timers.h new file mode 100644 index 0000000..3b40859 --- /dev/null +++ b/firmware/timers.h @@ -0,0 +1,12 @@ +#ifndef _TIMERS_H_INCLUDED +#define _TIMERS_H_INCLUDED + +void InitLED(void); +void SetLEDThreshold(int threshold); +void LEDBlink(void); +void LEDOff(void); + +void InitTicks(); +BYTE GetTickCount(void); + +#endif diff --git a/firmware/usb.c b/firmware/usb.c new file mode 100644 index 0000000..0796caa --- /dev/null +++ b/firmware/usb.c @@ -0,0 +1,518 @@ +#include "defs.h" +#include "string.h" +#include "timers.h" + +__xdata __at usb_buffer_VA volatile BYTE usb_buffer[1024]; + +BYTE bmRequestType; +BYTE bRequest; +WORD wValue; +WORD wIndex; +WORD wLength; + +static __xdata BYTE usb_irq; +static __xdata BYTE UsbIntStsF080, UsbIntStsF082, UsbIntStsF086, UsbIntStsF087; + +BYTE usb_speed; +__xdata volatile BYTE usb_received_data_ready, usb_have_csw_ready; + +extern BYTE scsi_status; +extern DWORD scsi_data_residue; +extern DWORD scsi_transfer_size; +extern BYTE scsi_tag[4]; +extern BYTE scsi_dir_in; +extern BYTE scsi_cdb[16]; +extern BYTE scsi_lun; +extern BYTE scsi_cdb_size; +extern BYTE HandleCDB(void); +extern volatile BYTE send_keys_enabled; + +extern BYTE HandleStandardRequest(void); +extern BYTE HandleClassRequest(void); +extern BYTE HandleVendorRequest(void); + +void SetDMA(BYTE p5, BYTE p3, BYTE px) +{ + XVAL(0xF80B) = 0; + XVAL(0xF80C) = p5-1; + + switch(px) + { + case 0: + { + XVAL(0xF80D) = p3; + XVAL(0xF80E) = p3; + break; + } + case 1: + { + XVAL(0xF80D) = p3; + break; + } + case 2: + { + XVAL(0xF80E) = p3; + break; + } + default: + { + break; + } + } +} + +void SendControlResponse(int size) +{ + EP0.len_l = LSB(size); + EP0.len_m = MSB(size); + EP0.len_h = 0; + EP0.cs = 0x40; + while (EP0.cs & 0x40); + EP0CS = 0x05; +} + +void SendData0(WORD size, BYTE offset) +{ + if (size > 0) + { + SetDMA(0x20, 0, 0); + SetDMA(0x20, 0x80, 1); + EP0.ptr_l = usb_buffer_PA>>8; + EP0.ptr_m = usb_buffer_PA>>16; + EP0.ptr_h = usb_buffer_PA>>24; + EP0.offset = offset; + EP0.len_l = LSB(size); + EP0.len_m = MSB(size); + EP0.len_h = 0; + EP0.cs = 0x88; + + while(EP0.cs & 0x80); + } +} + +void SendData1(WORD size, BYTE offset) +{ + if (size > 0) + { + SetDMA(0x20, 0, 0); + SetDMA(0x20, 0x80, 1); + EP1.ptr_l = usb_buffer_PA>>8; + EP1.ptr_m = usb_buffer_PA>>16; + EP1.ptr_h = usb_buffer_PA>>24; + EP1.offset = offset; + EP1.len_l = LSB(size); + EP1.len_m = MSB(size); + EP1.len_h = 0; + EP1.cs = 0x88; + + while(EP1.cs & 0x80); + } +} + +static void SendCSW() +{ + usb_buffer[0] = 'U'; + usb_buffer[1] = 'S'; + usb_buffer[2] = 'B'; + usb_buffer[3] = 'S'; + usb_buffer[4] = scsi_tag[0]; + usb_buffer[5] = scsi_tag[1]; + usb_buffer[6] = scsi_tag[2]; + usb_buffer[7] = scsi_tag[3]; + usb_buffer[8] = scsi_data_residue; + usb_buffer[9] = scsi_data_residue>>8; + usb_buffer[10] = scsi_data_residue>>16; + usb_buffer[11] = scsi_data_residue>>24; + usb_buffer[12] = scsi_status; + + SendData1(13, 0); + usb_have_csw_ready = 0; + scsi_data_residue = 0; +} + +static void SendCSW2() +{ + while(EP1.cs & bmSTALL); + while((EP1.r17 & 0x80)==0) + { + if ((XVAL(0xF010) & 0x20)==0) + { + usb_have_csw_ready = 0; + return; + } + } + + while(EP1.cs & 0x40); + while(EP2.cs & 0x40); + while(EP3.cs & 0x40); + while(EP4.cs & 0x40); + + EP1.fifo = 'U'; + EP1.fifo = 'S'; + EP1.fifo = 'B'; + EP1.fifo = 'S'; + EP1.fifo = scsi_tag[0]; + EP1.fifo = scsi_tag[1]; + EP1.fifo = scsi_tag[2]; + EP1.fifo = scsi_tag[3]; + EP1.fifo = scsi_data_residue; + EP1.fifo = scsi_data_residue>>8; + EP1.fifo = scsi_data_residue>>16; + EP1.fifo = scsi_data_residue>>24; + EP1.fifo = scsi_status; + EP1.len_l = 13; + EP1.len_m = 0; + EP1.len_h = 0; + EP1.cs = 0x40; + usb_have_csw_ready = 0; + scsi_data_residue = 0; +} + +void InitUSB(void) +{ + BYTE b; + + usb_irq = 0; + usb_received_data_ready = 0; + usb_have_csw_ready = 0; + usb_speed = 0; + EP1.ptr_l = usb_buffer_PA>>8; + EP1.ptr_m = usb_buffer_PA>>16; + EP1.ptr_h = usb_buffer_PA>>24; + EP1.r8 = 0x10; + EP1.offset = 0; + EP2.ptr_l = usb_buffer_PA>>8; + EP2.ptr_m = usb_buffer_PA>>16; + EP2.ptr_h = usb_buffer_PA>>24; + EP2.r8 = 0x10; + EP2.offset = 0; + + if (WARMSTATUS & 2) //USB warm start + { + if ((USBSTAT & bmSpeed) == bmSuperSpeed) + { + usb_speed = bmSuperSpeed; + } + else if ((USBSTAT & bmSpeed) == bmHighSpeed) + { + usb_speed = bmHighSpeed; + } + else if ((USBSTAT & bmSpeed) == bmFullSpeed) + { + usb_speed = bmFullSpeed; + } + else + { + usb_speed = 0; + } + + EX1 = 1; + EX0 = 1; + EPIE = bmEP2IRQ | bmEP4IRQ; + scsi_data_residue = 0; + scsi_status = 0; + SendCSW(); + } + else + { + //USB cold start + REGBANK = 6; + XVAL(0xF240) = 2; + XVAL(0xF28C) = 0x36; + XVAL(0xF28D) = 0xD0; + XVAL(0xF28E) = 0x98; + REGBANK = 0; + EPIE = bmEP2IRQ | bmEP4IRQ; + USBCTL = bmAttach | bmSuperSpeed; + + XVAL(0xFA38) |= 2; + + EX1 = 1; + EX0 = 1; + for (b = 0; b < 250; b++); + } +} + +void usb_isr(void) __interrupt USB_VECT +{ + usb_irq = USBIRQ; + + if (usb_irq & 0x20) + { + USBIRQ = 0x20; + } + + if (usb_irq & 0x10) + { + USBIRQ = 0x10; + } + + if (usb_irq & bmSpeedChange) + { + USBIRQ = bmSpeedChange; + if ((USBSTAT & bmSpeed) == bmSuperSpeed) + { + usb_speed = bmSuperSpeed; + } + else if ((USBSTAT & bmSpeed) == bmHighSpeed) + { + usb_speed = bmHighSpeed; + } + else if ((USBSTAT & bmSpeed) == bmFullSpeed) + { + usb_speed = bmFullSpeed; + } + else + { + usb_speed = 0; + } + } + + if (usb_irq & 0x40) + { + USBIRQ = 0x40; + } + + UsbIntStsF087 = XVAL(0xF087); + UsbIntStsF086 = XVAL(0xF086); + UsbIntStsF082 = XVAL(0xF082); + UsbIntStsF080 = XVAL(0xF080); + + if (UsbIntStsF082 & 0x80) + { + XVAL(0xF082) = 0x80; + } + + if (UsbIntStsF082 & 0x40) + { + XVAL(0xF082) = 0x40; + } + + if (UsbIntStsF080 & 1) + { + XVAL(0xF080) = 1; + if (EP0CS & bmSUDAV) + { + bmRequestType = SETUPDAT[0]; + bRequest = SETUPDAT[1]; + wValue = SETUPDAT[2] | (SETUPDAT[3] << 8); + wIndex = SETUPDAT[4] | (SETUPDAT[5] << 8); + wLength = SETUPDAT[6] | (SETUPDAT[7] << 8); + } + } + + if (XVAL(0xF082) & 0x20) + { + XVAL(0xF082) = 0x20; + } + + if (XVAL(0xF081) & 0x10) + { + XVAL(0xF081) = 0x10; + } + + if (XVAL(0xF081) & 0x20) + { + XVAL(0xF081) = 0x20; + } + + if (UsbIntStsF080 | UsbIntStsF082 | UsbIntStsF086 | UsbIntStsF087 | usb_irq) + { + EX0 = 0; + } +} + +void ep_isr(void) __interrupt EP_VECT +{ + BYTE interrupts = (EPIRQ & (bmEP2IRQ | bmEP4IRQ)); + if (interrupts & bmEP2IRQ) + { + EPIE &= ~bmEP2IRQ; //disable this + EPIRQ = bmEP2IRQ; //acknowledge it + usb_received_data_ready |= bmEP2IRQ; + } + + if (interrupts & bmEP4IRQ) + { + EPIE &= ~bmEP4IRQ; //disable this + EPIRQ = bmEP4IRQ; //acknowledge it + usb_received_data_ready |= bmEP4IRQ; + } +} + +static void ResetEPs() +{ + EPIE = bmEP2IRQ | bmEP4IRQ; + EP1.cs = 0; + EP2.cs = 0; + EP3.cs = 0; + EP4.cs = 0; +} + +static void HandleControlRequest(void) +{ + BYTE res; + switch(bmRequestType & 0x60) + { + case 0: + res = HandleStandardRequest(); + break; + case 0x20: + res = HandleClassRequest(); + break; + case 0x40: + res = HandleVendorRequest(); + break; + default: + res = FALSE; + } + + if (!res) + { + EP0CS = wLength ? bmEP0STALL : bmEP0NAK; + } +} + +void HandleUSBEvents(void) +{ + if (UsbIntStsF080 | UsbIntStsF082 | UsbIntStsF086 | UsbIntStsF087 | usb_irq) + { + if (usb_irq) + { + if (usb_irq & 0x40) + { + USBCTL &= ~bmAttach; + ResetEPs(); + XVAL(0xFE88) = 0; + XVAL(0xFE82) = 0x10; + while(XVAL(0xFE88)!=2); + USBCTL = bmAttach; + } + + if (usb_irq & bmSpeedChange) + { + ResetEPs(); + } + + usb_irq = 0; + } + else + { + if (UsbIntStsF082 & 0xC0) + { + ResetEPs(); + XVAL(0xF092) = 0; + XVAL(0xF096) = 0; + if (UsbIntStsF082 & 0x40) + { + XVAL(0xF07A) = 1; + } + } + else + { + if (UsbIntStsF080 & 1) + { + HandleControlRequest(); + } + } + + UsbIntStsF080 = 0; + UsbIntStsF082 = 0; + UsbIntStsF086 = 0; + UsbIntStsF087 = 0; + } + + EX0 = 1; + } + + //WHY DOESN'T THIS INTERRUPT FIRE?! + if (1)//usb_received_data_ready) + { + if (1)//usb_received_data_ready & bmEP4IRQ) + { + if (EP4.fifo_count > 0) + { + EP4.cs = 0x40; + + send_keys_enabled = 1; + usb_received_data_ready &= ~bmEP4IRQ; + EPIE |= bmEP4IRQ; + } + } + + if (usb_received_data_ready & bmEP2IRQ) + { + if (EP2.fifo_count == 31) //CBW size + { + BYTE a, b, c, d; + + scsi_data_residue = 0; + /*while(EP1.cs & 0x40); + while(EP2.cs & 0x40); + while(EP3.cs & 0x40); + while(EP4.cs & 0x40);*/ + + a = EP2.fifo; + b = EP2.fifo; + c = EP2.fifo; + d = EP2.fifo; + if ((a=='U') && (b=='S') && (c=='B') && (d=='C')) + { + scsi_tag[0] = EP2.fifo; + scsi_tag[1] = EP2.fifo; + scsi_tag[2] = EP2.fifo; + scsi_tag[3] = EP2.fifo; + scsi_transfer_size = EP2.fifo; + scsi_transfer_size |= ((DWORD)EP2.fifo)<<8; + scsi_transfer_size |= ((DWORD)EP2.fifo)<<16; + scsi_transfer_size |= ((DWORD)EP2.fifo)<<24; + scsi_dir_in = EP2.fifo & 0x80; + scsi_lun = EP2.fifo; + scsi_cdb_size = EP2.fifo; + for(a = 0; a < 16; a++) + { + scsi_cdb[a] = EP2.fifo; + } + + EP2.cs = 0x40; + if (!HandleCDB()) + { + scsi_status = 1; + if (scsi_transfer_size == 0) + { + EP1.cs = bmSTALL; + } + else if (scsi_dir_in) + { + EP1.cs = bmSTALL; + } + else + { + EP2.cs = bmSTALL; + } + } + + usb_have_csw_ready = 1; + } + else + { + EP2.cs = 0x40; + EP2.cs = 4; + } + } + else + { + EP2.cs = 0x40; + EP2.cs = 4; + } + + usb_received_data_ready &= ~bmEP2IRQ; + EPIE |= bmEP2IRQ; + } + } + + if (usb_have_csw_ready) + { + SendCSW2(); + } +} diff --git a/firmware/usb.h b/firmware/usb.h new file mode 100644 index 0000000..310fd66 --- /dev/null +++ b/firmware/usb.h @@ -0,0 +1,22 @@ +#ifndef _USB_H_INCLUDED +#define _USB_H_INCLUDED + +void InitUSB(void); +void HandleUSBEvents(void); +void SendControlResponse(int size); +void SendData0(WORD size, BYTE offset); +void SendData1(WORD size, BYTE offset); +void SetDMA(BYTE p5, BYTE p3, BYTE px); + +extern BYTE bmRequestType; +extern BYTE bRequest; +extern WORD wValue; +extern WORD wIndex; +extern WORD wLength; + +extern BYTE usb_speed; +extern __xdata __at usb_buffer_VA volatile BYTE usb_buffer[1024]; +extern __xdata volatile BYTE usb_received_data_ready; +extern __xdata volatile BYTE usb_have_csw_ready; + +#endif diff --git a/patch/base.c b/patch/base.c new file mode 100644 index 0000000..04945b3 --- /dev/null +++ b/patch/base.c @@ -0,0 +1,370 @@ +#include "defs.h" +#include "equates.h" + +#define FEATURE_CHANGE_PASSWORD +//#define FEATURE_EXPOSE_HIDDEN_PARTITION + +#define NUM_LBAS 0xE6EA40UL //this needs to be even! (round down) + +//SCSI command codes +#define SCSI_06 0x06 +#define SCSI_06_XPEEK 0x06 +#define SCSI_06_XPOKE 0x07 +#define SCSI_06_IPEEK 0x08 +#define SCSI_06_IPOKE 0x09 +#define SCSI_START_STOP_UNIT 0x1B +#define SCSI_READ_FORMAT_CAPACITIES 0x23 +#define SCSI_READ_CAPACITY 0x25 +#define SCSI_READ_SECTOR 0x28 +#define SCSI_WRITE_SECTOR 0x2A + +void memset(BYTE* s, BYTE c, int size) +{ + int i; + for (i = 0; i < size; i++) + { + *s = c; + s++; + } +} + +void SendData(int size) +{ + int i; + + while(EP1.cs & bmSTALL); + while((EP1.r17 & 0x80)==0) + { + if ((XVAL(0xF010) & 0x20)==0) + { + return; + } + } + + while(EP1.cs & 0x40); + while(EP2.cs & 0x40); + while(EP3.cs & 0x40); + while(EP4.cs & 0x40); + + for (i = 0; i < size; i++) + { + EP1.fifo = EPBUF[i]; + } + + EP1.len_l = size & 0xFF; + EP1.len_m = (size >> 8) & 0xFF; + EP1.len_h = 0; + EP1.cs = 0x40; +} + +void SendCSW(void) +{ + memset(EPBUF, 0, 13); + EPBUF[0] = 'U'; + EPBUF[1] = 'S'; + EPBUF[2] = 'B'; + EPBUF[3] = 'S'; + EPBUF[4] = scsi_tag[3]; + EPBUF[5] = scsi_tag[2]; + EPBUF[6] = scsi_tag[1]; + EPBUF[7] = scsi_tag[0]; + SendData(13); +} + +//Disconnects and then re-enumerates. +void RecycleUSBConnection(void) +{ + USBCTL &= ~bmAttach; + EPIE = bmEP2IRQ; + EP1.cs = 0; + EP2.cs = 0; + XVAL(0xFE88) = 0; + XVAL(0xFE82) = 0x10; + while (XVAL(0xFE88) != 2); + USBCTL = bmAttach; +} + +#ifdef FEATURE_EXPOSE_HIDDEN_PARTITION + +//HACK: We're using an unused bit of SYSTEM register 0xFA38 to hold the hidden status, +// since we don't yet know what RAM is safe to use. +BOOL IsHiddenAreaVisible(void) +{ + return WARMSTATUS & 0x80; +} + +//HACK: We're using an unused bit of SYSTEM register 0xFA38 to hold the hidden status, +// since we don't yet know what RAM is safe to use. +void SetHiddenAreaVisibility(BOOL visible) +{ + if (visible) + { + WARMSTATUS |= 0x80; + } + else + { + WARMSTATUS &= 0x7F; + } +} + +void WaitTenSeconds(void) +{ + WORD i, j; + + for (i = 0; i < 65535; i++) + { + for (j = 0; j < 1000; j++) + { + //Do nothing + } + } +} + +#endif + +/* +void HandleControlRequest(void) +{ + if (bmRequestType & 0x20) + { + //Handle class request + } + else if (bmRequestType & 0x40) + { + //Handle vendor request + } + else + { + //Handle standard request + } +} +*/ + +/* +void EndpointInterrupt(void) +{ + __asm + push ACC + push DPH + push DPL + //If no interrupts fired, get out + mov DPTR, #EPIRQ + movx A, @DPTR + jz 000001$ + //Let the firmware know these events happened, so it can handle them + mov B, A + mov DPTR, #FW_EPIRQ + movx A, @DPTR + orl A, B + movx @DPTR, A + //Disable those interrupts so they don't fire again until we're done with them + mov A, #0xFF + xrl A, B + mov DPTR, #EPIE + movx @DPTR, A + //Acknowledge the interrupts + mov A, B + mov DPTR, #EPIRQ + movx @DPTR, A +000001$:pop DPL + pop DPH + pop ACC + reti + __endasm; +} + +void HandleEndpointInterrupt(void) +{ + //Handle incoming endpoint data +} +*/ + +#ifdef FEATURE_EXPOSE_HIDDEN_PARTITION + +void HandleCDB(void) +{ + unsigned long lba; + + switch(scsi_cdb[0]) + { + case SCSI_06: + { + switch (scsi_cdb[1]) + { + case SCSI_06_XPEEK: + { + EPBUF[0] = XVAL((scsi_cdb[2] << 8) | scsi_cdb[3]); + SendData(1); + break; + } + case SCSI_06_XPOKE: + { + XVAL((scsi_cdb[2] << 8) | scsi_cdb[3]) = scsi_cdb[4]; + SendData(1); + break; + } + case SCSI_06_IPEEK: + { + EPBUF[0] = IVAL(scsi_cdb[2]); + SendData(1); + break; + } + case SCSI_06_IPOKE: + { + IVAL(scsi_cdb[2]) = scsi_cdb[3]; + SendData(1); + break; + } + default: + { + __asm + ljmp #DEFAULT_CDB_HANDLER + __endasm; + } + } + break; + } + case SCSI_START_STOP_UNIT: + { + //Are we being stopped? + if (scsi_cdb[4] == 0x02) + { + //Yes, set the other section as the visible one + SetHiddenAreaVisibility(!IsHiddenAreaVisible()); + + //Send the CSW + SendCSW(); + + //Wait and re-enumerate + WaitTenSeconds(); + RecycleUSBConnection(); + } + else + { + //No, let things continue normally + __asm + ljmp #DEFAULT_CDB_HANDLER + __endasm; + } + break; + } + case SCSI_READ_FORMAT_CAPACITIES: + { + lba = NUM_LBAS / 2; + + memset(EPBUF, 0, 12); + EPBUF[3] = 0x08; //capacity list length + EPBUF[4] = lba >> 24; + EPBUF[5] = lba >> 16; + EPBUF[6] = lba >> 8; + EPBUF[7] = lba & 0xFF; + EPBUF[8] = 0x02; //descriptor code (formatted media) + EPBUF[10] = 0x02; //block length (512 bytes/sector) + SendData(12); + break; + } + case SCSI_READ_CAPACITY: + { + lba = (NUM_LBAS / 2) - 1; + + memset(EPBUF, 0, 8); + EPBUF[0] = lba >> 24; + EPBUF[1] = lba >> 16; + EPBUF[2] = lba >> 8; + EPBUF[3] = lba & 0xFF; + EPBUF[6] = 0x02; //block length (512 bytes/sector) + SendData(8); + break; + } + case SCSI_READ_SECTOR: //TODO: we should handle the other READ(X) commands as well + { + //Get the passed-in LBA + lba = ((unsigned long)(scsi_cdb[2]) << 24) & 0xFF000000; + lba |= ((unsigned long)(scsi_cdb[3]) << 16) & 0xFF0000; + lba |= (scsi_cdb[4] << 8) & 0xFF00; + lba |= scsi_cdb[5]; + + //Shift it if necessary + if (IsHiddenAreaVisible()) + { + lba += NUM_LBAS / 2; + } + + //Save it + scsi_cdb[2] = (lba >> 24) & 0xFF; + scsi_cdb[3] = (lba >> 16) & 0xFF; + scsi_cdb[4] = (lba >> 8) & 0xFF; + scsi_cdb[5] = lba & 0xFF; + + //Let the firmware do its thing + __asm + ljmp #DEFAULT_READ_SECTOR_HANDLER + __endasm; + } + case SCSI_WRITE_SECTOR: //TODO: we should handle the other WRITE(x) commands as well + { + //Get the passed-in LBA + lba = ((unsigned long)(scsi_cdb[2]) << 24) & 0xFF000000; + lba |= ((unsigned long)(scsi_cdb[3]) << 16) & 0xFF0000; + lba |= (scsi_cdb[4] << 8) & 0xFF00; + lba |= scsi_cdb[5]; + + //Shift it if necessary + if (IsHiddenAreaVisible()) + { + lba += NUM_LBAS / 2; + } + + //Save it + scsi_cdb[2] = (lba >> 24) & 0xFF; + scsi_cdb[3] = (lba >> 16) & 0xFF; + scsi_cdb[4] = (lba >> 8) & 0xFF; + scsi_cdb[5] = lba & 0xFF; + + //Let the firmware do its thing + __asm + ljmp #DEFAULT_CDB_HANDLER + __endasm; + } + default: + __asm + ljmp #DEFAULT_CDB_HANDLER + __endasm; + } +} + +#endif + +//Called in the firmware's infinite loop. +/* +void LoopDo(void) +{ +} +*/ + +#ifdef FEATURE_CHANGE_PASSWORD + +void SetPassword(BYTE* address) +{ + int i; + for (i = 0; i < 16; i++) + { + *(address + i) = 'A'; + } +} + +void PasswordReceived() +{ + if (EPBUF[0]) + { + SetPassword(EPBUF); + + } + + if (EPBUF[0x10]) + { + SetPassword(EPBUF + 0x10); + } +} + +#endif diff --git a/patch/build.bat b/patch/build.bat new file mode 100644 index 0000000..95c98ee --- /dev/null +++ b/patch/build.bat @@ -0,0 +1,50 @@ +@ECHO OFF + +REM Set things up and create bin directory if necessary. +SETLOCAL ENABLEDELAYEDEXPANSION +SET BUILD_FILES= +IF NOT EXIST bin\NUL MKDIR bin + +REM Generate .h C file for compilation. +ECHO *** Generating C .h file... +..\tools\Injector.exe /action=GenerateHFile /firmware=fw.bin /output=equates.h +IF ERRORLEVEL 1 GOTO ERRORS + +REM Build each file in the list. +REM NOTE: This needs to change if more code files or sections are added. +FOR %%A IN ( +base +) DO ( +ECHO *** Building %%A.c... +sdcc --model-small -mmcs51 -pdefcpu -c -obin\%%A.rel %%A.c +IF ERRORLEVEL 1 GOTO ERRORS +SET "BUILD_FILES=!BUILD_FILES! bin\%%A.rel" +) + +REM Retrieve free space for each section in the image. +ECHO *** Retrieving free space in image... +..\tools\Injector.exe /action=FindFreeBlock /firmware=fw.bin /section=Base /output=bin\free.txt +SET BASE_FREE_ADDR= +FOR /F "delims=" %%i IN (bin\free.txt) DO SET BASE_FREE_ADDR=!BASE_FREE_ADDR! %%i +DEL bin\free.txt + +REM Build Intel Hex and BIN versions of combined file. +ECHO *** Linking... +sdcc --model-small --code-loc %BASE_FREE_ADDR% --xram-size 0x400 --xram-loc 0x7C00 -o bin\output.hex %BUILD_FILES% +..\tools\hex2bin bin\output.hex + +REM Build patched image from assembled image. +REM NOTE: This needs to change if more code files or sections are added. +ECHO *** Injecting... +..\tools\Injector.exe /action=ApplyPatches /firmware=fw.bin /basecode=bin\output.bin /baserst=bin\base.rst /output=bin\fw.bin +IF ERRORLEVEL 1 GOTO ERRORS + +GOTO END + +:ERRORS +ECHO *** There were errors^^! *** + +:END +ECHO *** Done. + +ENDLOCAL diff --git a/patch/defs.h b/patch/defs.h new file mode 100644 index 0000000..d5d1c4c --- /dev/null +++ b/patch/defs.h @@ -0,0 +1,287 @@ +#ifndef DEFS_H +#define DEFS_H + +#define MSB(word) (BYTE)(((WORD)(word) >> 8) & 0xff) +#define LSB(word) (BYTE)((WORD)(word) & 0xff) + +#define XVAL(addr) (*( __xdata volatile unsigned char *)(addr)) +#define IVAL(addr) (*( __idata volatile unsigned char *)(addr)) + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef __bit BOOL; +typedef __bit bit; +#define TRUE 1 +#define FALSE 0 + +#define USB_VECT 0 +#define TMR0_VECT 1 +#define EP_VECT 2 +#define TMR1_VECT 3 +#define COM0_VECT 4 + +#define bmAttach 0x80 +#define bmSpeed 7 +#define bmSuperSpeed 4 +#define bmHighSpeed 0 +#define bmFullSpeed 1 +#define bmSpeedChange 0x80 +#define bmEP2IRQ 2 +#define bmEP0ACK 1 +#define bmEP0NAK 2 +#define bmEP0IN 4 +#define bmEP0STALL 8 +#define bmSUDAV 0x80 +#define bmSTALL 2 + +#define bmNandReady 1 + +#define bmNandDma0 0 +#define bmNandDma1 0x80 +#define bmNandDmaRead 0 +#define bmNandDmaWrite 0x40 + +#define bmDmaCmd 7 +#define bmDmaCopy 2 +#define bmDmaFill 4 +#define bmDmaWidth8 0 +#define bmDmaWidth16 0x40 +#define bmDmaWidth32 0x80 + +#define bmPRAM 1 + +// ------------------------------------------------------------------------------------------------ +// * SFRs +// * ------------------------------------------------------------------------------------------------ +// BYTE Register +__sfr __at (0x80) P0 ; +__sfr __at (0x90) P1 ; +__sfr __at (0xA0) P2 ; +__sfr __at (0xB0) P3 ; +__sfr __at (0xD0) PSW ; +__sfr __at (0xE0) ACC ; +__sfr __at (0xF0) B ; +__sfr __at (0x81) SP ; +__sfr __at (0x82) DPL ; +__sfr __at (0x83) DPH ; +__sfr __at (0x87) PCON; +__sfr __at (0x88) TCON; +__sfr __at (0x89) TMOD; +__sfr __at (0x8A) TL0 ; +__sfr __at (0x8B) TL1 ; +__sfr __at (0x8C) TH0 ; +__sfr __at (0x8D) TH1 ; +__sfr __at (0xA8) IE ; +__sfr __at (0xB8) IP ; +__sfr __at (0x98) SCON; +__sfr __at (0x99) SBUF; + +/* BIT Register */ +/* PSW */ +__sbit __at (0xD7) CY ; +__sbit __at (0xD6) AC ; +__sbit __at (0xD5) F0 ; +__sbit __at (0xD4) RS1 ; +__sbit __at (0xD3) RS0 ; +__sbit __at (0xD2) OV ; +__sbit __at (0xD0) P ; + +/* TCON */ +__sbit __at (0x8F) TF1 ; +__sbit __at (0x8E) TR1 ; +__sbit __at (0x8D) TF0 ; +__sbit __at (0x8C) TR0 ; +__sbit __at (0x8B) IE1 ; +__sbit __at (0x8A) IT1 ; +__sbit __at (0x89) IE0 ; +__sbit __at (0x88) IT0 ; + +/* IE */ +__sbit __at (0xAF) EA ; +__sbit __at (0xAC) ES ; +__sbit __at (0xAB) ET1 ; +__sbit __at (0xAA) EX1 ; +__sbit __at (0xA9) ET0 ; +__sbit __at (0xA8) EX0 ; + +/* IP */ +__sbit __at (0xBC) PS ; +__sbit __at (0xBB) PT1 ; +__sbit __at (0xBA) PX1 ; +__sbit __at (0xB9) PT0 ; +__sbit __at (0xB8) PX0 ; + +/* P3 */ +__sbit __at (0xB7) RD ; +__sbit __at (0xB6) WR ; +__sbit __at (0xB5) T1 ; +__sbit __at (0xB4) T0 ; +__sbit __at (0xB3) INT1; +__sbit __at (0xB2) INT0; +__sbit __at (0xB1) TXD ; +__sbit __at (0xB0) RXD ; + +/* SCON */ +__sbit __at (0x9F) SM0 ; +__sbit __at (0x9E) SM1 ; +__sbit __at (0x9D) SM2 ; +__sbit __at (0x9C) REN ; +__sbit __at (0x9B) TB8 ; +__sbit __at (0x9A) RB8 ; +__sbit __at (0x99) TI ; +__sbit __at (0x98) RI ; + +// ------------------------------------------------------------------------------------------------ +// Xdata F000-F3FF USB Registers +// ------------------------------------------------------------------------------------------------ +// some banking registers switching +// value: 0-7, default: 0 +__xdata __at 0xF000 volatile BYTE REGBANK; +__xdata __at 0xF008 volatile BYTE USBCTL; +__xdata __at 0xF009 volatile BYTE USBSTAT; +__xdata __at 0xF027 volatile BYTE USBIRQ; +__xdata __at 0xF020 volatile BYTE EPIRQ; +__xdata __at 0xF030 volatile BYTE EPIE; +__xdata __at 0xF048 volatile BYTE EP0CS; +__xdata __at 0xF0B8 volatile BYTE SETUPDAT[8]; + +typedef struct +{ + BYTE r0,r1,r2,r3,r4; + BYTE ptr_l, ptr_m, ptr_h; //buffer ptr = buf_pa>>8 + BYTE r8,r9; + BYTE ofs; // buffer offset, data addr will be ptr<<8 + ofs*0x200 + BYTE rB; + BYTE len_l, len_m, len_h; //C,D,E + BYTE rF,r10,r11,r12; + BYTE cs; //13 + BYTE r14,r15,r16,r17,r18,r19; + BYTE fifo_count; + BYTE r1B; + BYTE fifo; //1C +} EPREGS; + +__xdata __at 0xF1C0 volatile EPREGS EP0; +__xdata __at 0xF200 volatile EPREGS EP1; +__xdata __at 0xF240 volatile EPREGS EP2; +__xdata __at 0xF280 volatile EPREGS EP3; +__xdata __at 0xF2C0 volatile EPREGS EP4; + +typedef struct +{ + BYTE raw_cmd; + BYTE u1[3]; + BYTE raw_addr; + BYTE u5[3]; + BYTE raw_data; + BYTE r9; + BYTE uA[2]; + BYTE rC; + BYTE uD[3]; + BYTE r10; + BYTE u11[7]; + BYTE r18; + BYTE u19[5]; + BYTE status; // .0 - R/nB + BYTE u1F[0x19]; + BYTE r38, r39, r3A; + BYTE u3B; + BYTE r3C, r3D; + BYTE u3E[2]; + BYTE r40; + BYTE dma_size; // DMA size in KB + BYTE r42; + BYTE dma_mode; // DMA modes + BYTE u44[3]; + BYTE r47; + BYTE u48[0x14]; + BYTE r5C; // nand command + // DMA command. |=1 to go, wait until .0 cleared + BYTE dma_cmd; + BYTE u61[0x0B]; + BYTE dma1_page; // DMA1 start page. Autoincrements + BYTE u6D[3]; + BYTE dma0_page; // DMA0 start page. Autoincrements + BYTE u71[3]; + // DMA1 PA. This pseudo reg sets dma1_page actually. RAZ + BYTE dma1_ptr0, dma1_ptr1, dma1_ptr2, dma1_ptr3; + // DMA0 PA. This pseudo reg sets w_dma_page actually. RAZ + BYTE dma0_ptr0, dma0_ptr1, dma0_ptr2, dma0_ptr3; + BYTE u7C[4]; + BYTE r80; + BYTE u81[0x1B]; + BYTE page_size_l, page_size_h; // 9C full page size with spare + BYTE r9E, r9F; + BYTE uA0[0x4C]; + BYTE rEC; + BYTE uED[0x13]; +} NANDREGS; + +__xdata __at 0xF400 volatile NANDREGS NFC0; +__xdata __at 0xF500 volatile NANDREGS NFC1; + +__xdata __at 0xF608 volatile BYTE NANDCSOUT; +__xdata __at 0xF618 volatile BYTE NANDCSDIR; +//F638, F639 - scrambler control +//F638 | 18 & 7F - turn off descrambler +__xdata __at 0xF700 volatile NANDREGS NFCX; + +// DMA copy source / fill destination physical address +__xdata __at 0xF900 volatile BYTE DMASRCL; +__xdata __at 0xF901 volatile BYTE DMASRCM; +__xdata __at 0xF902 volatile BYTE DMASRCH; + +// DMA copy destination physical address +__xdata __at 0xF904 volatile BYTE DMADSTL; +__xdata __at 0xF905 volatile BYTE DMADSTM; +__xdata __at 0xF906 volatile BYTE DMADSTH; + +// DMA copy size in bytes (always in bytes, regardless of cmd width) +__xdata __at 0xF908 volatile BYTE DMASIZEL; +__xdata __at 0xF909 volatile BYTE DMASIZEM; +__xdata __at 0xF90A volatile BYTE DMASIZEH; + +// DMA fill value +__xdata __at 0xF90C volatile BYTE DMAFILL0; +__xdata __at 0xF90D volatile BYTE DMAFILL1; +__xdata __at 0xF90E volatile BYTE DMAFILL2; +__xdata __at 0xF90F volatile BYTE DMAFILL3; + +// DMA command +__xdata __at 0xF930 volatile BYTE DMACMD; + +// ------------------------------------------------------------------------------------------------ +// Xdata FA00-FAFF SYSTEM Registers +// ------------------------------------------------------------------------------------------------ +__xdata __at 0xFA14 volatile BYTE GPIO0DIR; +__xdata __at 0xFA15 volatile BYTE GPIO0OUT; +__xdata __at 0xFA38 volatile BYTE WARMSTATUS; + +// XDATA banking +// XDATA consists of 3 mapped areas: BANK0,1,2 +// BANK0 start is fixed at 0000, BANK1,2 starts at variable addresses +// in case of overlapped addresses a bank with higher number has higher priority + +// xdata BANK0 mapping registers +// maps XDATA VA 0000 to PA=BANK0PAH:L<<9, size - up to BANK1/2 +__xdata __at 0xFA40 volatile BYTE BANK0PAL; +__xdata __at 0xFA41 volatile BYTE BANK0PAH; + +// xdata BANK1 mapping registers +// maps XDATA VA=(BANK1VA & 0xFE)<<8 to PA=BANK1PAH:L<<9, size - up to BANK2 +__xdata __at 0xFA42 volatile BYTE BANK1VA; +__xdata __at 0xFA43 volatile BYTE BANK1PAL; +__xdata __at 0xFA44 volatile BYTE BANK1PAH; + +// xdata BANK2 mapping registers +// maps XDATA VA=(BANK2VA & 0xFE)<<8 to PA=BANK2PAH:L<<9, size - up to F000 +__xdata __at 0xFA45 volatile BYTE BANK2VA; +__xdata __at 0xFA46 volatile BYTE BANK2PAL; +__xdata __at 0xFA47 volatile BYTE BANK2PAH; + +// PRAM/PROM switching with restart +// value: bit0 =0 - run from PROM, =1 - run from PRAM. Changing this bit causes CPU restart! +__xdata __at 0xFA48 volatile BYTE PRAMCTL; + +#endif diff --git a/templates/BNdummy.bin b/templates/BNdummy.bin new file mode 100644 index 0000000000000000000000000000000000000000..6bfee1ddee681c9aa046fa95a070e1e0955c0aad GIT binary patch literal 33792 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 C61{5x literal 0 HcmV?d00001 diff --git a/templates/FWdummy.bin b/templates/FWdummy.bin new file mode 100644 index 0000000000000000000000000000000000000000..49c8ac52598022d88291c1733bd78cebdf4dab4a GIT binary patch literal 205824 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 GSK!}Yej$DU literal 0 HcmV?d00001