Add project files.

This commit is contained in:
Matt Nadareski
2019-05-17 20:34:39 -07:00
parent 2cc1526ccd
commit b40b419a19
9 changed files with 16242 additions and 0 deletions

25
Reheader.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reheader", "Reheader\Reheader.csproj", "{113C73A6-2299-4987-8215-464D446AB8BC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{113C73A6-2299-4987-8215-464D446AB8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{113C73A6-2299-4987-8215-464D446AB8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{113C73A6-2299-4987-8215-464D446AB8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{113C73A6-2299-4987-8215-464D446AB8BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5ECC5E34-310A-4002-B289-8207B530D284}
EndGlobalSection
EndGlobal

129
Reheader/Enumerations.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Reheader
{
public enum GameConsole
{
NONE,
Dendy,
Famicom,
NesNtsc,
NesPal,
Playchoice,
VsSystem,
}
public enum Mirroring
{
NONE,
Vertical,
Horizontal,
FourScreen,
// Unknown
A,
}
public static class Extensions
{
/// <summary>
/// Get long name representation for a GameConsole
/// </summary>
/// <param name="console">GameConsole value</param>
/// <returns>Long name representation of input, if possible</returns>
public static string Name(this GameConsole console)
{
switch (console)
{
case GameConsole.Dendy:
return "Steepler Dendy";
case GameConsole.Famicom:
return "Nintendo Famicom";
case GameConsole.NesNtsc:
return "Nintendo Entertainment System (NTSC)";
case GameConsole.NesPal:
return "Nintendo Entertainment System (PAL)";
case GameConsole.Playchoice:
return "Nintendo PlayChoice-10";
case GameConsole.VsSystem:
return "Nintendo Vs. System";
default:
return "UNKNOWN";
}
}
/// <summary>
/// Get long name representation for a Mirroring
/// </summary>
/// <param name="mirroring">Mirroring value</param>
/// <returns>Long name representation of input, if possible</returns>
public static string Name(this Mirroring mirroring)
{
switch (mirroring)
{
case Mirroring.Vertical:
return "Vertical";
case Mirroring.Horizontal:
return "Horizontal";
case Mirroring.A:
return "A (Unknown)";
case Mirroring.FourScreen:
return "Four-Screen VRAM";
default:
return "UNKNOWN";
}
}
/// <summary>
/// Get GameConsole value from a string
/// </summary>
/// <param name="console">String value</param>
/// <returns>GameConsole representation of the input, if possible</returns>
public static GameConsole GameConsoleFromString(string console)
{
switch (console)
{
case "Dendy":
return GameConsole.Dendy;
case "Famicom":
return GameConsole.Famicom;
case "NesNtsc":
return GameConsole.NesNtsc;
case "NesPal":
return GameConsole.NesPal;
case "Playchoice":
return GameConsole.Playchoice;
case "VsSystem":
return GameConsole.VsSystem;
default:
return GameConsole.NONE;
}
}
/// <summary>
/// Get Mirroring value from a string
/// </summary>
/// <param name="mirroring">String value</param>
/// <returns>Mirroring representation of the input, if possible</returns>
public static Mirroring MirroringFromString(string mirroring)
{
switch (mirroring)
{
case "h":
return Mirroring.Horizontal;
case "v":
return Mirroring.Vertical;
case "4":
return Mirroring.FourScreen;
case "a":
return Mirroring.A;
default:
return Mirroring.NONE;
}
}
}
}

188
Reheader/HeaderInfo.cs Normal file
View File

@@ -0,0 +1,188 @@
using System;
namespace Reheader
{
public class HeaderInfo
{
public string CRC { get; set; }
public GameConsole System { get; set; }
public string Board { get; set; }
public string PCB { get; set; }
public string Chip { get; set; }
public int Mapper { get; set; }
public int PrgRomSizeKb { get; set; }
public int ChrRomSizeKb { get; set; }
public int ChrRamSizeKb { get; set; }
public int WorkRamSizeKb { get; set; }
public int SaveRamSizeKb { get; set; }
public bool Battery { get; set; }
public Mirroring Mirroring { get; set; }
public string ControllerType { get; set; }
public string BusConflicts { get; set; }
public int SubMapper { get; set; }
public string VsSystemType { get; set; }
public string PpuModel { get; set; }
public HeaderInfo(string csvLine)
{
if (string.IsNullOrEmpty(csvLine))
return;
var csvElements = csvLine.Split(',');
if (csvElements.Length != 18)
return;
this.CRC = csvElements[0];
this.System = Extensions.GameConsoleFromString(csvElements[1]);
this.Board = csvElements[2];
this.PCB = csvElements[3];
this.Chip = csvElements[4];
this.Mapper = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[5]) ? "-1" : csvElements[5]);
this.PrgRomSizeKb = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[6]) ? "-1" : csvElements[6]);
this.ChrRomSizeKb = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[7]) ? "-1" : csvElements[7]);
this.ChrRamSizeKb = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[8]) ? "-1" : csvElements[8]);
this.WorkRamSizeKb = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[9]) ? "-1" : csvElements[9]);
this.SaveRamSizeKb = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[10]) ? "-1" : csvElements[10]);
this.Battery = (csvElements[11] == "1");
this.Mirroring = Extensions.MirroringFromString(csvElements[12]);
this.ControllerType = csvElements[13];
this.BusConflicts = csvElements[14];
this.SubMapper = Int32.Parse(string.IsNullOrWhiteSpace(csvElements[15]) ? "-1" : csvElements[15]);
this.VsSystemType = csvElements[16];
this.PpuModel = csvElements[17];
}
/// <summary>
/// Generate an iNES 1.0 or 2.0 header for the current Header information
/// </summary>
/// <param name="versionTwo">True if iNES 2.0 header should be created, false for iNES 1.0</param>
/// <returns>Byte array representing the header, null on error</returns>
public byte[] GenerateHeader(bool versionTwo)
{
if (this.CRC == null)
return null;
byte[] header = new byte[16];
// NES signature
header[0] = 0x4e;
header[1] = 0x45;
header[2] = 0x53;
header[3] = 0x1a;
// PRG ROM in 16KB units
header[4] = (byte)(this.PrgRomSizeKb / 16);
// CHR ROM in 8KB units
header[5] = (byte)(this.ChrRamSizeKb > 0 ? 0 : this.ChrRomSizeKb / 8);
// Mirroring, Battery, Trainer, Mapper
header[6] = 0;
// Mirroring
if (this.Mirroring == Mirroring.Horizontal)
header[6] &= 0x00;
else if (this.Mirroring == Mirroring.Vertical)
header[6] &= 0x01;
else if (this.Mirroring == Mirroring.FourScreen)
header[6] &= 0x08;
// Battery
if (this.Battery)
header[6] &= 0x02;
// Trainer
//if (this.Trainer)
// header[6] &= 0x04;
// Lower nibble of mapper
header[6] &= (byte)(((byte)(this.Mapper & 0x0F)) << 4);
// VS/Playchoice, NES 2.0, Mapper
header[7] = 0;
// VS Unisystem and PlayChoice-10
if (this.System == GameConsole.VsSystem)
header[7] &= 0x01;
else if (this.System == GameConsole.Playchoice)
header[7] &= 0x02;
// NES 2.0 Format
if (versionTwo)
header[7] &= 0x08;
// Upper nibble of mapper
header[7] &= (byte)(this.Mapper & 0xF0);
// iNES 1.0 Format
if (!versionTwo)
{
// PRG-RAM size
header[8] = (byte)(this.WorkRamSizeKb / 8);
// TV System
header[9] = 0;
if (this.System == GameConsole.Famicom || this.System == GameConsole.NesNtsc)
header[9] &= 0x00;
else if (this.System == GameConsole.Dendy || this.System == GameConsole.NesPal)
header[9] &= 0x01;
// TV system, PRG-RAM presence, bus conflicts
header[10] = 0;
// TV System... again
if (this.System == GameConsole.Famicom || this.System == GameConsole.NesNtsc)
header[9] &= 0x00;
else if (this.System == GameConsole.Dendy || this.System == GameConsole.NesPal)
header[9] &= 0x02;
// PRG-RAM presence
if (this.WorkRamSizeKb > 0)
header[9] &= 0x10;
// Bus conflicts
if (!string.IsNullOrEmpty(this.BusConflicts) && this.BusConflicts != "N")
header[9] &= 0x20;
// Padding
}
// iNES 2.0 Format
else
{
// Mapper MSB, Submapper
header[8] = 0;
// Uppermost nibble of mapper
header[8] &= (byte)((byte)(this.Mapper & 0xF00) >> 8);
// Submapper
header[8] &= (byte)(this.SubMapper << 4);
// PRG-ROM/CHR-ROM size MSB
//...
// https://wiki.nesdev.com/w/index.php/NES_2.0
}
return header;
}
}
}

15553
Reheader/MesenDB.txt Normal file

File diff suppressed because it is too large Load Diff

153
Reheader/OptimizedCRC.cs Normal file
View File

@@ -0,0 +1,153 @@
/*
Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System;
namespace Reheader
{
public class OptimizedCRC : IDisposable
{
private const uint kCrcPoly = 0xEDB88320;
private const uint kInitial = 0xFFFFFFFF;
private const int CRC_NUM_TABLES = 8;
private static readonly uint[] Table;
static OptimizedCRC()
{
unchecked
{
Table = new uint[256 * CRC_NUM_TABLES];
int i;
for (i = 0; i < 256; i++)
{
uint r = (uint)i;
for (int j = 0; j < 8; j++)
{
r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
}
Table[i] = r;
}
for (; i < 256 * CRC_NUM_TABLES; i++)
{
uint r = Table[i - 256];
Table[i] = Table[r & 0xFF] ^ (r >> 8);
}
}
}
public uint UnsignedValue;
public OptimizedCRC()
{
Init();
}
/// <summary>
/// Reset CRC
/// </summary>
public void Init()
{
UnsignedValue = kInitial;
}
public int Value
{
get { return (int)~UnsignedValue; }
}
public void Update(byte[] data, int offset, int count)
{
new ArraySegment<byte>(data, offset, count); // check arguments
if (count == 0)
{
return;
}
var table = OptimizedCRC.Table;
uint crc = UnsignedValue;
for (; (offset & 7) != 0 && count != 0; count--)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
if (count >= 8)
{
/*
* Idea from 7-zip project sources (http://7-zip.org/sdk.html)
*/
int end = (count - 8) & ~7;
count -= end;
end += offset;
while (offset != end)
{
crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
offset += 8;
crc = table[(byte)crc + 0x700]
^ table[(byte)(crc >>= 8) + 0x600]
^ table[(byte)(crc >>= 8) + 0x500]
^ table[/*(byte)*/(crc >> 8) + 0x400]
^ table[(byte)(high) + 0x300]
^ table[(byte)(high >>= 8) + 0x200]
^ table[(byte)(high >>= 8) + 0x100]
^ table[/*(byte)*/(high >> 8) + 0x000];
}
}
while (count-- != 0)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
UnsignedValue = crc;
}
static public int Compute(byte[] data, int offset, int count)
{
var crc = new OptimizedCRC();
crc.Update(data, offset, count);
return crc.Value;
}
static public int Compute(byte[] data)
{
return Compute(data, 0, data.Length);
}
static public int Compute(ArraySegment<byte> block)
{
return Compute(block.Array, block.Offset, block.Count);
}
public void Dispose()
{
UnsignedValue = 0;
}
}
}

19
Reheader/Program.cs Normal file
View File

@@ -0,0 +1,19 @@
using System;
namespace Reheader
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: Reheader.exe <file|dir> ...");
return;
}
var reheader = new Reheader();
reheader.Process(args);
}
}
}

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Reheader": {
"commandName": "Project",
"commandLineArgs": "\"C:\\Users\\Matt\\GitHub\\Reheader\\Reheader\\bin\\Debug\\netcoreapp2.1\\games\""
}
}
}

150
Reheader/Reheader.cs Normal file
View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Reheader
{
public class Reheader
{
private const string mappingFile = "MesenDB.txt";
private readonly Dictionary<string, HeaderInfo> headerMapping = new Dictionary<string, HeaderInfo>();
public Reheader()
{
if (!File.Exists(mappingFile))
return;
// Populate the header mapping dictionary
using (TextReader reader = File.OpenText(mappingFile))
{
string line = string.Empty;
while ((line = reader.ReadLine()) != null)
{
// Comment lines start with #
if (line.StartsWith("#"))
continue;
var headerInfo = new HeaderInfo(line);
headerMapping.Add(headerInfo.CRC, headerInfo);
}
}
}
/// <summary>
/// Process a list of paths that may contain unheadered NES files
/// </summary>
/// <param name="paths"></param>
public void Process(string[] paths)
{
foreach (string path in paths)
{
if (File.Exists(path))
{
if (!this.AddHeader(Path.GetFullPath(path), Path.GetFullPath(path) + "-headered"))
Console.WriteLine($"Could not find a header for {path}");
}
else if (Directory.Exists(path))
{
foreach (string file in Directory.EnumerateFiles(Path.GetFullPath(path), "*", SearchOption.AllDirectories))
if (!this.AddHeader(Path.GetFullPath(file), Path.GetFullPath(file) + "-headered"))
Console.WriteLine($"Could not find a header for {file}");
}
else
{
Console.WriteLine($"{path} is not a file or directory");
}
}
}
/// <summary>
/// Add an iNES header to a given input file, if applicable
/// </summary>
/// <param name="infile">Input file to check and header</param>
/// <param name="outfile">Output file path</param>
/// <returns>True if a header was able to be generated and added, false otherwise</returns>
private bool AddHeader(string infile, string outfile)
{
Console.WriteLine($"Processing {infile}");
if (!File.Exists(infile))
return false;
// Get the CRC hash of the file
string crc = GetCRCFromFile(infile).ToUpperInvariant();
// See if that's a known CRC
if (!headerMapping.ContainsKey(crc))
return false;
// Get the mapping
var header = headerMapping[crc];
// Get the new bytes
var headerBytes = header.GenerateHeader(false);
using (BinaryReader reader = new BinaryReader(File.OpenRead(infile)))
using (BinaryWriter writer = new BinaryWriter(File.Create(outfile)))
{
// Write the header
writer.Write(headerBytes);
// Now the rest
var tempBytes = reader.ReadBytes((int)reader.BaseStream.Length);
writer.Write(tempBytes);
writer.Flush();
}
Console.WriteLine($"Found header for {infile}. Wrote new file to {outfile}.");
return true;
}
/// <summary>
/// Get the CRC32 hash for a file, if possible
/// </summary>
/// <param name="file">Path to the file</param>
/// <returns>String representation of the CRC32 hash of the file</returns>
private string GetCRCFromFile(string file)
{
using (OptimizedCRC crc = new OptimizedCRC())
using (BinaryReader reader = new BinaryReader(File.OpenRead(file)))
{
byte[] buffer = new byte[8 * 1024];
int read = Int32.MaxValue;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
{
crc.Update(buffer, 0, read);
}
crc.Update(buffer, 0, 0);
byte[] crcHash = BitConverter.GetBytes(crc.Value).Reverse().ToArray();
return ByteArrayToString(crcHash);
}
}
/// <summary>
/// Convert a byte array to a hex string
/// </summary>
/// <param name="bytes">Byte array to convert</param>
/// <returns>Hex string representing the byte array</returns>
/// <link>http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa</link>
private string ByteArrayToString(byte[] bytes)
{
// If we get null in, we send null out
if (bytes == null)
return null;
try
{
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", string.Empty).ToLowerInvariant();
}
catch
{
return null;
}
}
}
}

17
Reheader/Reheader.csproj Normal file
View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net462</TargetFrameworks>
<RuntimeIdentifiers>win10-x64;win7-x86</RuntimeIdentifiers>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<None Update="MesenDB.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>