Browse Source

Initial commit.

* Mostly complete header coverage, albeit with no real display
      options.
    * Does not currently read the publisher data.
master
Ian Burgmyer 4 years ago
commit
d02496d44b
  1. 261
      .gitignore
  2. 16
      DMGHeader.sln
  3. 8
      DMGHeader/CgbMode.cs
  4. 8
      DMGHeader/DMGHeader.csproj
  5. 7
      DMGHeader/DestinationCode.cs
  6. 6
      DMGHeader/DmgEntryPoint.cs
  7. 198
      DMGHeader/DmgHeader.cs
  8. 7
      DMGHeader/JumpType.cs
  9. 34
      DMGHeader/MemorySize.cs
  10. 85
      DMGHeader/Program.cs
  11. 25
      LICENSE

261
.gitignore vendored

@ -0,0 +1,261 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

16
DMGHeader.sln

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DMGHeader", "DMGHeader\DMGHeader.csproj", "{4FF16F68-6FAD-4924-935B-78322B35BF6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FF16F68-6FAD-4924-935B-78322B35BF6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FF16F68-6FAD-4924-935B-78322B35BF6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FF16F68-6FAD-4924-935B-78322B35BF6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FF16F68-6FAD-4924-935B-78322B35BF6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

8
DMGHeader/CgbMode.cs

@ -0,0 +1,8 @@
namespace DMGHeader {
public enum CgbMode {
Disabled,
PaletteOnly,
Enhanced,
Required
}
}

8
DMGHeader/DMGHeader.csproj

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

7
DMGHeader/DestinationCode.cs

@ -0,0 +1,7 @@
namespace DMGHeader {
public enum DestinationCode {
Japanese,
NonJapanese,
Unknown
}
}

6
DMGHeader/DmgEntryPoint.cs

@ -0,0 +1,6 @@
namespace DMGHeader {
public class DmgEntryPoint {
public JumpType Jump { get; set; } = JumpType.None;
public int JumpAddress { get; set; } = -1;
}
}

198
DMGHeader/DmgHeader.cs

@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
namespace DMGHeader {
public class DmgHeader {
public DmgEntryPoint EntryPoint { get; set; }
public bool ValidLogo { get; set; }
public string Title { get; set; }
public CgbMode CgbMode { get; set; }
public bool SgbSupport { get; set; }
public string CartridgeType { get; set; }
public MemorySize RomSize { get; set; }
public MemorySize ExternalRamSize { get; set; }
public DestinationCode DestinationCode { get; set; }
public byte MaskRomVersion { get; set; }
public byte HeaderChecksum { get; set; }
public bool ValidHeaderChecksum { get; set; }
public UInt16 CartridgeChecksum { get; set; }
private byte[] _validLogo = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
};
public DmgHeader(string filename) {
using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
using var br = new BinaryReader(fs);
fs.Seek(0x100, SeekOrigin.Begin);
EntryPoint = ReadEntryPoint(br.ReadBytes(4));
ValidLogo = ValidateLogo(br.ReadBytes(_validLogo.Length));
// See if the upper bit of the CGB flag is set to see how much of the title field to read.
fs.Seek(0x143, SeekOrigin.Begin);
var cgb = (br.ReadByte() & 0b10000000) != 0;
fs.Seek(0x134, SeekOrigin.Begin);
var titleLen = cgb ? 15 : 16;
Title = ReadTitle(br.ReadBytes(titleLen));
CgbMode = cgb ? ReadCgbMode(br.ReadByte()) : CgbMode.Disabled;
fs.Seek(0x146, SeekOrigin.Begin);
SgbSupport = br.ReadByte() == 0x03;
CartridgeType = ReadCartridgeType(br.ReadByte());
RomSize = ReadRomSize(br.ReadByte());
ExternalRamSize = ReadExternalRamSize(br.ReadByte());
DestinationCode = ReadDestinationCode(br.ReadByte());
fs.Seek(0x14C, SeekOrigin.Begin);
MaskRomVersion = br.ReadByte();
HeaderChecksum = br.ReadByte();
fs.Seek(0x134, SeekOrigin.Begin);
ValidHeaderChecksum = HeaderChecksum == CalculateHeaderChecksum(br.ReadBytes(0x19));
fs.Seek(0x14E, SeekOrigin.Begin);
CartridgeChecksum = br.ReadUInt16();
}
private byte CalculateHeaderChecksum(byte[] data) {
byte x = 0;
unchecked {
x = data.Aggregate(x, (current, b) => (byte)(current - b - 1));
}
return x;
}
private string ReadCartridgeType(byte type) {
switch(type) {
case 0x00: return "ROM ONLY";
case 0x01: return "MBC1";
case 0x02: return "MBC1+RAM";
case 0x03: return "MBC1+RAM+BATTERY";
case 0x05: return "MBC2";
case 0x06: return "MBC2+BATTERY";
case 0x08: return "ROM+RAM";
case 0x09: return "ROM+RAM+BATTERY";
case 0x0B: return "MMM01";
case 0x0C: return "MMM01+RAM";
case 0x0D: return "MMM01+RAM+BATTERY";
case 0x0F: return "MBC3+TIMER+BATTERY";
case 0x10: return "MBC3+TIMER+RAM+BATTERY";
case 0x11: return "MBC3";
case 0x12: return "MBC3+RAM";
case 0x13: return "MBC3+RAM+BATTERY";
case 0x19: return "MBC5";
case 0x1A: return "MBC5+RAM";
case 0x1B: return "MBC5+RAM+BATTERY";
case 0x1C: return "MBC5+RUMBLE";
case 0x1D: return "MBC5+RUMBLE+RAM";
case 0x1E: return "MBC5+RUMBLE+RAM+BATTERY";
case 0x20: return "MBC6";
case 0x22: return "MBC7+SENSOR+RUMBLE+RAM+BATTERY";
case 0xFC: return "POCKET CAMERA";
case 0xFD: return "BANDAI TAMA5";
case 0xFE: return "HuC3";
case 0xFF: return "HuC1+RAM+BATTERY";
default: return "Unknown";
}
}
private CgbMode ReadCgbMode(byte mode) {
if((mode & 0b10000000) == 0) return CgbMode.Disabled;
if((mode & 0b01000000) != 0) return CgbMode.Required;
if((mode & 0b00001100) != 0) return CgbMode.PaletteOnly;
return CgbMode.Enhanced;
}
private DestinationCode ReadDestinationCode(byte data) {
switch(data) {
case 0x00: return DestinationCode.Japanese;
case 0x01: return DestinationCode.NonJapanese;
default: return DestinationCode.Unknown;
}
}
private DmgEntryPoint ReadEntryPoint(byte[] data) {
var entry = new DmgEntryPoint();
var i = 0;
for(; i < data.Length; i++) {
if(data[i] != 0xC3 && data[i] != 0x18) continue;
entry.Jump = data[i] == 0xC3 ? JumpType.Absolute : JumpType.Relative;
break;
}
if(entry.Jump == JumpType.None) return entry;
var addrLen = entry.Jump == JumpType.Absolute ? 2 : 1;
var addr = data.Skip(++i).Take(addrLen).ToArray();
if(addr.Length != addrLen) return entry;
if(entry.Jump == JumpType.Absolute) {
// Stitch the values into a 16-bit value.
entry.JumpAddress = addr[1] << 8 | addr[0];
} else {
entry.JumpAddress = addr[0] + 0x100 + i;
}
return entry;
}
private MemorySize ReadExternalRamSize(byte data) {
var size = new MemorySize(8);
switch(data) {
case 0x00: size.Size = 0; return size;
case 0x01: size.Size = 2; return size;
case 0x02: size.Size = 8; return size;
case 0x03: size.Size = 32; return size;
case 0x04: size.Size = 128; return size;
case 0x05: size.Size = 64; return size;
default: return null;
}
}
private MemorySize ReadRomSize(byte data) {
var size = new MemorySize(16);
switch(data) {
case 0x00: size.Size = 32; return size;
case 0x01: size.Size = 64; return size;
case 0x02: size.Size = 128; return size;
case 0x03: size.Size = 256; return size;
case 0x04: size.Size = 512; return size;
case 0x05: size.Size = 1024; return size;
case 0x06: size.Size = 2048; return size;
case 0x07: size.Size = 4096; return size;
case 0x08: size.Size = 8192; return size;
case 0x52: size.Size = 1152; return size;
case 0x53: size.Size = 1280; return size;
case 0x54: size.Size = 1536; return size;
default: return null;
}
}
private string ReadTitle(byte[] data) {
// Convert nulls to spaces.
for(var i = 0; i < data.Length; i++) {
if(data[i] == 0) data[i] = 32;
}
return Encoding.ASCII.GetString(data).Trim();
}
private bool ValidateLogo(byte[] data) =>
data != null && data.Length == _validLogo.Length && data.SequenceEqual(_validLogo);
}
}

7
DMGHeader/JumpType.cs

@ -0,0 +1,7 @@
namespace DMGHeader {
public enum JumpType {
None,
Absolute,
Relative
}
}

34
DMGHeader/MemorySize.cs

@ -0,0 +1,34 @@
namespace DMGHeader {
public class MemorySize {
private int _size;
public int Banks { get; private set; }
public int BankSize { get; }
public int Size {
get => _size;
set {
_size = value;
if(_size <= BankSize * 2)
Banks = 0;
else
Banks = _size / BankSize;
}
}
public MemorySize(int bankSize) {
BankSize = bankSize;
}
public override string ToString() {
var ret = $"{Size} KB ";
if(Banks == 0)
ret += "(no bank switching)";
else
ret += $"({Banks} banks of {BankSize} KB each)";
return ret;
}
}
}

85
DMGHeader/Program.cs

@ -0,0 +1,85 @@
using System;
using System.IO;
namespace DMGHeader {
internal static class Program {
internal static void DumpHeader(DmgHeader header) {
Console.Write("Entry Point: ");
if(header.EntryPoint.Jump == JumpType.None) {
Console.WriteLine("No jump found.");
} else {
var jumpType = header.EntryPoint.Jump == JumpType.Absolute ? "Absolute" : "Relative";
Console.WriteLine($"{jumpType} jump to ROM position 0x{header.EntryPoint.JumpAddress:X4}");
}
Console.WriteLine($"Logo Check: {(header.ValidLogo ? "PASSED" : "FAILED")}");
Console.WriteLine($"Title/Manufacturer Code: {header.Title}");
Console.Write("CGB Mode: ");
switch(header.CgbMode) {
case CgbMode.Disabled:
Console.WriteLine("Disabled");
break;
case CgbMode.PaletteOnly:
Console.WriteLine("Palette Only");
break;
case CgbMode.Enhanced:
Console.WriteLine("Enhanced");
break;
case CgbMode.Required:
Console.WriteLine("Required");
break;
default:
Console.WriteLine("Unknown");
break;
}
Console.WriteLine($"SGB: {(header.SgbSupport ? "Supported" : "Not Supported")}");
Console.WriteLine($"Cartridge Type: {header.CartridgeType}");
Console.WriteLine($"ROM Size: {header.RomSize}");
Console.WriteLine($"Cartridge RAM Size: {header.ExternalRamSize}");
Console.Write("Destination Code: ");
switch(header.DestinationCode) {
case DestinationCode.Japanese:
Console.WriteLine("Japanese");
break;
case DestinationCode.NonJapanese:
Console.WriteLine("Non-Japanese");
break;
case DestinationCode.Unknown:
Console.WriteLine("Unknown/Invalid Code");
break;
default:
Console.WriteLine("-- INVALID ENUM VALUE -- ");
break;
}
Console.WriteLine($"Mask ROM Version: 0x{header.MaskRomVersion:X2}");
Console.WriteLine($"Header Checksum: 0x{header.HeaderChecksum:X2}");
Console.WriteLine($"Header Checksum Status: {(header.ValidHeaderChecksum ? "VALID" : "NOT VALID")}");
Console.WriteLine($"Cartridge Checksum: 0x{header.CartridgeChecksum:X4}");
Console.WriteLine();
}
private static int Main(string[] args) {
if(args.Length == 0) {
Console.WriteLine("usage: DMGHeader [filename]");
return 0;
}
var filename = args[0];
if(!File.Exists(filename)) {
Console.WriteLine("File not found!");
return 1;
}
var header = new DmgHeader(filename);
DumpHeader(header);
return 0;
}
}
}

25
LICENSE

@ -0,0 +1,25 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org/>
Loading…
Cancel
Save