Initial commit

This commit is contained in:
ApfelTeeSaft
2025-05-15 14:33:41 +02:00
commit 810f1458cb
35 changed files with 9258 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+398
View File
@@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.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
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# 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
# Note: 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
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable 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
*.appx
*.appxbundle
*.appxupload
# 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
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# 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
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# 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/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 ApfelTeeSaft
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.
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NESDecompiler.Core\NESDecompiler.Core.csproj" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>
</Project>
+214
View File
@@ -0,0 +1,214 @@
using System;
using System.IO;
using CommandLine;
using NESDecompiler.Core.ROM;
using NESDecompiler.Core.Disassembly;
using NESDecompiler.Core.Decompilation;
using NESDecompiler.Core.Exceptions;
using System.Runtime.InteropServices;
namespace NESDecompiler.CLI
{
/// <summary>
/// Command-line options for the decompiler
/// </summary>
class Options
{
[Option('i', "input", Required = true, HelpText = "Input NES ROM file path")]
public string InputFile { get; set; } = string.Empty;
[Option('o', "output", Required = false, HelpText = "Output directory for generated files")]
public string? OutputDirectory { get; set; }
[Option('d', "disassemble", Required = false, Default = false, HelpText = "Generate disassembly output")]
public bool GenerateDisassembly { get; set; }
[Option('c', "decompile", Required = false, Default = true, HelpText = "Generate C code output")]
public bool GenerateCCode { get; set; }
[Option('v', "verbose", Required = false, Default = false, HelpText = "Set output to verbose messages")]
public bool Verbose { get; set; }
}
/// <summary>
/// Main program class for the CLI interface
/// </summary>
class Program
{
/// <summary>
/// Entry point for the CLI application
/// </summary>
static int Main(string[] args)
{
return Parser.Default.ParseArguments<Options>(args)
.MapResult(
options => RunDecompiler(options),
errors => 1
);
}
/// <summary>
/// Runs the decompiler with the specified options
/// </summary>
/// <param name="options">Command-line options</param>
/// <returns>0 for success, non-zero for failure</returns>
static int RunDecompiler(Options options)
{
try
{
Console.WriteLine($"NES ROM Decompiler");
Console.WriteLine($"=================");
Console.WriteLine();
if (!File.Exists(options.InputFile))
{
Console.Error.WriteLine($"Error: Input file '{options.InputFile}' does not exist");
return 1;
}
string outputDirectory = options.OutputDirectory ?? Path.GetDirectoryName(options.InputFile) ?? ".";
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
Console.WriteLine($"Loading ROM: {options.InputFile}");
var romLoader = new ROMLoader();
var romInfo = romLoader.LoadFromFile(options.InputFile);
if (options.Verbose)
{
Console.WriteLine(romInfo.ToString());
}
byte[] prgRomData = romLoader.GetPRGROMData();
Console.WriteLine("Disassembling code...");
var disassembler = new Disassembler(romInfo, prgRomData);
disassembler.Disassemble();
if (options.Verbose)
{
Console.WriteLine($"Disassembled {disassembler.Instructions.Count} instructions");
}
if (options.GenerateDisassembly)
{
string disassemblyFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".asm");
Console.WriteLine($"Generating disassembly: {disassemblyFile}");
string disassembly = disassembler.ToAssemblyString();
File.WriteAllText(disassemblyFile, disassembly);
}
if (options.GenerateCCode)
{
Console.WriteLine("Decompiling to C code...");
var decompiler = new Decompiler(romInfo, disassembler);
decompiler.Decompile();
if (options.Verbose)
{
Console.WriteLine($"Identified {decompiler.Variables.Count} variables and {decompiler.Functions.Count} functions");
}
string cCodeFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".c");
Console.WriteLine($"Generating C code: {cCodeFile}");
string cCode = decompiler.GenerateCCode();
File.WriteAllText(cCodeFile, cCode);
string headerFile = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(options.InputFile) + ".h");
Console.WriteLine($"Generating header file: {headerFile}");
string headerCode = GenerateHeaderFile(decompiler);
File.WriteAllText(headerFile, headerCode);
}
Console.WriteLine("Decompilation completed successfully");
return 0;
}
catch (Exception ex)
{
Exception currentEx = ex;
Console.Error.WriteLine($"Error: {currentEx.Message}");
if (options.Verbose)
{
while (currentEx.InnerException != null)
{
currentEx = currentEx.InnerException;
Console.Error.WriteLine($" Caused by: {currentEx.Message}");
}
Console.Error.WriteLine();
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(ex.StackTrace);
}
return 1;
}
}
/// <summary>
/// Generates a C header file for the decompiled ROM
/// </summary>
/// <param name="decompiler">The decompiler instance</param>
/// <returns>The generated header code</returns>
static string GenerateHeaderFile(Decompiler decompiler)
{
StringWriter writer = new StringWriter();
writer.WriteLine("/*");
writer.WriteLine(" * Decompiled NES ROM");
writer.WriteLine($" * ROM: {decompiler.ROMInfo}");
writer.WriteLine(" */");
writer.WriteLine();
string guardName = Path.GetFileNameWithoutExtension(decompiler.ROMInfo.RawData[0].ToString()).ToUpper() + "_H";
writer.WriteLine($"#ifndef {guardName}");
writer.WriteLine($"#define {guardName}");
writer.WriteLine();
writer.WriteLine("#include <stdint.h>");
writer.WriteLine("#include <stdbool.h>");
writer.WriteLine();
writer.WriteLine("// NES Hardware Registers");
writer.WriteLine("#define PPUCTRL (*((volatile uint8_t*)0x2000))");
writer.WriteLine("#define PPUMASK (*((volatile uint8_t*)0x2001))");
writer.WriteLine("#define PPUSTATUS (*((volatile uint8_t*)0x2002))");
writer.WriteLine("#define OAMADDR (*((volatile uint8_t*)0x2003))");
writer.WriteLine("#define OAMDATA (*((volatile uint8_t*)0x2004))");
writer.WriteLine("#define PPUSCROLL (*((volatile uint8_t*)0x2005))");
writer.WriteLine("#define PPUADDR (*((volatile uint8_t*)0x2006))");
writer.WriteLine("#define PPUDATA (*((volatile uint8_t*)0x2007))");
writer.WriteLine("#define OAMDMA (*((volatile uint8_t*)0x4014))");
writer.WriteLine("#define SND_CHN (*((volatile uint8_t*)0x4015))");
writer.WriteLine("#define JOY1 (*((volatile uint8_t*)0x4016))");
writer.WriteLine("#define JOY2 (*((volatile uint8_t*)0x4017))");
writer.WriteLine();
writer.WriteLine("// Variables");
foreach (var variable in decompiler.Variables.Values)
{
if (variable.Address < 0x2000 || variable.Address >= 0x8000)
{
writer.WriteLine($"extern {variable.GetCType()} {variable.Name};");
}
}
writer.WriteLine();
writer.WriteLine("// Functions");
foreach (var function in decompiler.Functions.Values)
{
writer.WriteLine($"void {function.Name}();");
}
writer.WriteLine();
writer.WriteLine($"#endif // {guardName}");
return writer.ToString();
}
}
}
+400
View File
@@ -0,0 +1,400 @@
using System;
using System.Collections.Generic;
namespace NESDecompiler.Core.CPU
{
/// <summary>
/// Addressing modes for 6502 CPU instructions
/// </summary>
public enum AddressingMode
{
Implied, // No operand (e.g., RTS)
Accumulator, // Operand is the accumulator (e.g., LSR A)
Immediate, // Operand is 8-bit value (e.g., LDA #$12)
ZeroPage, // Operand is 8-bit address (e.g., LDA $12)
ZeroPageX, // Operand is 8-bit address, X-indexed (e.g., LDA $12,X)
ZeroPageY, // Operand is 8-bit address, Y-indexed (e.g., LDX $12,Y)
Relative, // Operand is 8-bit signed offset (e.g., BNE $12)
Absolute, // Operand is 16-bit address (e.g., LDA $1234)
AbsoluteX, // Operand is 16-bit address, X-indexed (e.g., LDA $1234,X)
AbsoluteY, // Operand is 16-bit address, Y-indexed (e.g., LDA $1234,Y)
Indirect, // Operand is 16-bit address pointing to the address (e.g., JMP ($1234))
IndexedIndirect, // Operand is 8-bit address, X-indexed, pointing to 16-bit address (e.g., LDA ($12,X))
IndirectIndexed // Operand is 8-bit address pointing to 16-bit address, Y-indexed (e.g., LDA ($12),Y)
}
/// <summary>
/// Instruction type categories
/// </summary>
public enum InstructionType
{
Load, // Load operations (LDA, LDX, LDY)
Store, // Store operations (STA, STX, STY)
Transfer, // Register transfers (TAX, TXA, etc.)
Stack, // Stack operations (PHA, PLA, PHP, PLP, TSX, TXS)
Arithmetic, // Arithmetic operations (ADC, SBC)
Increment, // Increment operations (INC, INX, INY)
Decrement, // Decrement operations (DEC, DEX, DEY)
Shift, // Shift operations (ASL, LSR, ROL, ROR)
Logic, // Logic operations (AND, EOR, ORA)
Compare, // Compare operations (CMP, CPX, CPY)
Branch, // Branch operations (BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS)
Jump, // Jump operations (JMP, JSR)
Return, // Return operations (RTS, RTI)
Set, // Set flag operations (SEC, SED, SEI)
Clear, // Clear flag operations (CLC, CLD, CLI, CLV)
Interrupt, // Interrupt operations (BRK)
Other // Other operations (NOP, ???)
}
/// <summary>
/// Information about a 6502 CPU instruction
/// </summary>
public class InstructionInfo
{
/// <summary>
/// The opcode value (0x00-0xFF)
/// </summary>
public byte Opcode { get; set; }
/// <summary>
/// The mnemonic for this instruction (e.g., "LDA", "STA")
/// </summary>
public string Mnemonic { get; set; }
/// <summary>
/// The addressing mode for this instruction
/// </summary>
public AddressingMode AddressingMode { get; set; }
/// <summary>
/// The size of this instruction in bytes (including operands)
/// </summary>
public byte Size { get; set; }
/// <summary>
/// The number of cycles this instruction takes to execute
/// </summary>
public byte Cycles { get; set; }
/// <summary>
/// Whether this instruction can take an additional cycle on page boundary crossing
/// </summary>
public bool ExtraCycleOnPageCross { get; set; }
/// <summary>
/// The instruction type category
/// </summary>
public InstructionType Type { get; set; }
/// <summary>
/// Whether this instruction is a valid 6502 instruction
/// </summary>
public bool IsValid { get; set; } = true;
public InstructionInfo(byte opcode, string mnemonic, AddressingMode addressingMode,
byte size, byte cycles, bool extraCycleOnPageCross, InstructionType type)
{
Opcode = opcode;
Mnemonic = mnemonic;
AddressingMode = addressingMode;
Size = size;
Cycles = cycles;
ExtraCycleOnPageCross = extraCycleOnPageCross;
Type = type;
}
/// <summary>
/// Returns a string representation of the operand format for this addressing mode
/// </summary>
public string GetOperandFormat()
{
return AddressingMode switch
{
AddressingMode.Implied => "",
AddressingMode.Accumulator => "A",
AddressingMode.Immediate => "#$%02X",
AddressingMode.ZeroPage => "$%02X",
AddressingMode.ZeroPageX => "$%02X,X",
AddressingMode.ZeroPageY => "$%02X,Y",
AddressingMode.Relative => "$%02X", // Will be processed specially for branches
AddressingMode.Absolute => "$%04X",
AddressingMode.AbsoluteX => "$%04X,X",
AddressingMode.AbsoluteY => "$%04X,Y",
AddressingMode.Indirect => "($%04X)",
AddressingMode.IndexedIndirect => "($%02X,X)",
AddressingMode.IndirectIndexed => "($%02X),Y",
_ => "???"
};
}
}
/// <summary>
/// Defines the 6502 CPU instruction set
/// </summary>
public static class InstructionSet
{
// The full 6502 instruction set
private static readonly Dictionary<byte, InstructionInfo> instructions = new Dictionary<byte, InstructionInfo>();
// Maps mnemonic + addressing mode to an instruction info object
private static readonly Dictionary<(string, AddressingMode), InstructionInfo> mnemonicMap =
new Dictionary<(string, AddressingMode), InstructionInfo>();
/// <summary>
/// Static constructor to initialize the instruction set
/// </summary>
static InstructionSet()
{
InitializeInstructionSet();
}
/// <summary>
/// Gets an instruction by its opcode
/// </summary>
/// <param name="opcode">The opcode value</param>
/// <returns>Information about the instruction</returns>
public static InstructionInfo GetInstruction(byte opcode)
{
if (instructions.TryGetValue(opcode, out var instruction))
{
return instruction;
}
// Return unknown instruction
return new InstructionInfo(
opcode, "???", AddressingMode.Implied, 1, 2, false, InstructionType.Other)
{
IsValid = false
};
}
/// <summary>
/// Gets an instruction by its mnemonic and addressing mode
/// </summary>
/// <param name="mnemonic">The instruction mnemonic</param>
/// <param name="addressingMode">The addressing mode</param>
/// <returns>Information about the instruction, or null if not found</returns>
public static InstructionInfo? GetInstruction(string mnemonic, AddressingMode addressingMode)
{
var key = (mnemonic.ToUpper(), addressingMode);
if (mnemonicMap.TryGetValue(key, out var instruction))
{
return instruction;
}
return null;
}
/// <summary>
/// Initializes the 6502 instruction set
/// </summary>
private static void InitializeInstructionSet()
{
// Load/Store Operations
Add(0xA9, "LDA", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
Add(0xA5, "LDA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
Add(0xB5, "LDA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Load);
Add(0xAD, "LDA", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
Add(0xBD, "LDA", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Load);
Add(0xB9, "LDA", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Load);
Add(0xA1, "LDA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Load);
Add(0xB1, "LDA", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Load);
Add(0xA2, "LDX", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
Add(0xA6, "LDX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
Add(0xB6, "LDX", AddressingMode.ZeroPageY, 2, 4, false, InstructionType.Load);
Add(0xAE, "LDX", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
Add(0xBE, "LDX", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Load);
Add(0xA0, "LDY", AddressingMode.Immediate, 2, 2, false, InstructionType.Load);
Add(0xA4, "LDY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Load);
Add(0xB4, "LDY", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Load);
Add(0xAC, "LDY", AddressingMode.Absolute, 3, 4, false, InstructionType.Load);
Add(0xBC, "LDY", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Load);
Add(0x85, "STA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
Add(0x95, "STA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Store);
Add(0x8D, "STA", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
Add(0x9D, "STA", AddressingMode.AbsoluteX, 3, 5, false, InstructionType.Store);
Add(0x99, "STA", AddressingMode.AbsoluteY, 3, 5, false, InstructionType.Store);
Add(0x81, "STA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Store);
Add(0x91, "STA", AddressingMode.IndirectIndexed, 2, 6, false, InstructionType.Store);
Add(0x86, "STX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
Add(0x96, "STX", AddressingMode.ZeroPageY, 2, 4, false, InstructionType.Store);
Add(0x8E, "STX", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
Add(0x84, "STY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Store);
Add(0x94, "STY", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Store);
Add(0x8C, "STY", AddressingMode.Absolute, 3, 4, false, InstructionType.Store);
// Register Transfers
Add(0xAA, "TAX", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
Add(0x8A, "TXA", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
Add(0xA8, "TAY", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
Add(0x98, "TYA", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
Add(0xBA, "TSX", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
Add(0x9A, "TXS", AddressingMode.Implied, 1, 2, false, InstructionType.Transfer);
// Stack Operations
Add(0x48, "PHA", AddressingMode.Implied, 1, 3, false, InstructionType.Stack);
Add(0x68, "PLA", AddressingMode.Implied, 1, 4, false, InstructionType.Stack);
Add(0x08, "PHP", AddressingMode.Implied, 1, 3, false, InstructionType.Stack);
Add(0x28, "PLP", AddressingMode.Implied, 1, 4, false, InstructionType.Stack);
// Logical Operations
Add(0x29, "AND", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
Add(0x25, "AND", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
Add(0x35, "AND", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
Add(0x2D, "AND", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
Add(0x3D, "AND", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
Add(0x39, "AND", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
Add(0x21, "AND", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
Add(0x31, "AND", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
Add(0x49, "EOR", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
Add(0x45, "EOR", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
Add(0x55, "EOR", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
Add(0x4D, "EOR", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
Add(0x5D, "EOR", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
Add(0x59, "EOR", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
Add(0x41, "EOR", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
Add(0x51, "EOR", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
Add(0x09, "ORA", AddressingMode.Immediate, 2, 2, false, InstructionType.Logic);
Add(0x05, "ORA", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
Add(0x15, "ORA", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Logic);
Add(0x0D, "ORA", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
Add(0x1D, "ORA", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Logic);
Add(0x19, "ORA", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Logic);
Add(0x01, "ORA", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Logic);
Add(0x11, "ORA", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Logic);
Add(0x24, "BIT", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Logic);
Add(0x2C, "BIT", AddressingMode.Absolute, 3, 4, false, InstructionType.Logic);
// Arithmetic Operations
Add(0x69, "ADC", AddressingMode.Immediate, 2, 2, false, InstructionType.Arithmetic);
Add(0x65, "ADC", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Arithmetic);
Add(0x75, "ADC", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Arithmetic);
Add(0x6D, "ADC", AddressingMode.Absolute, 3, 4, false, InstructionType.Arithmetic);
Add(0x7D, "ADC", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Arithmetic);
Add(0x79, "ADC", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Arithmetic);
Add(0x61, "ADC", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Arithmetic);
Add(0x71, "ADC", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Arithmetic);
Add(0xE9, "SBC", AddressingMode.Immediate, 2, 2, false, InstructionType.Arithmetic);
Add(0xE5, "SBC", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Arithmetic);
Add(0xF5, "SBC", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Arithmetic);
Add(0xED, "SBC", AddressingMode.Absolute, 3, 4, false, InstructionType.Arithmetic);
Add(0xFD, "SBC", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Arithmetic);
Add(0xF9, "SBC", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Arithmetic);
Add(0xE1, "SBC", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Arithmetic);
Add(0xF1, "SBC", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Arithmetic);
Add(0xC9, "CMP", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
Add(0xC5, "CMP", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
Add(0xD5, "CMP", AddressingMode.ZeroPageX, 2, 4, false, InstructionType.Compare);
Add(0xCD, "CMP", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
Add(0xDD, "CMP", AddressingMode.AbsoluteX, 3, 4, true, InstructionType.Compare);
Add(0xD9, "CMP", AddressingMode.AbsoluteY, 3, 4, true, InstructionType.Compare);
Add(0xC1, "CMP", AddressingMode.IndexedIndirect, 2, 6, false, InstructionType.Compare);
Add(0xD1, "CMP", AddressingMode.IndirectIndexed, 2, 5, true, InstructionType.Compare);
Add(0xE0, "CPX", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
Add(0xE4, "CPX", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
Add(0xEC, "CPX", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
Add(0xC0, "CPY", AddressingMode.Immediate, 2, 2, false, InstructionType.Compare);
Add(0xC4, "CPY", AddressingMode.ZeroPage, 2, 3, false, InstructionType.Compare);
Add(0xCC, "CPY", AddressingMode.Absolute, 3, 4, false, InstructionType.Compare);
// Increments & Decrements
Add(0xE6, "INC", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Increment);
Add(0xF6, "INC", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Increment);
Add(0xEE, "INC", AddressingMode.Absolute, 3, 6, false, InstructionType.Increment);
Add(0xFE, "INC", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Increment);
Add(0xE8, "INX", AddressingMode.Implied, 1, 2, false, InstructionType.Increment);
Add(0xC8, "INY", AddressingMode.Implied, 1, 2, false, InstructionType.Increment);
Add(0xC6, "DEC", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Decrement);
Add(0xD6, "DEC", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Decrement);
Add(0xCE, "DEC", AddressingMode.Absolute, 3, 6, false, InstructionType.Decrement);
Add(0xDE, "DEC", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Decrement);
Add(0xCA, "DEX", AddressingMode.Implied, 1, 2, false, InstructionType.Decrement);
Add(0x88, "DEY", AddressingMode.Implied, 1, 2, false, InstructionType.Decrement);
// Shifts
Add(0x0A, "ASL", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
Add(0x06, "ASL", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
Add(0x16, "ASL", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
Add(0x0E, "ASL", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
Add(0x1E, "ASL", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
Add(0x4A, "LSR", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
Add(0x46, "LSR", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
Add(0x56, "LSR", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
Add(0x4E, "LSR", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
Add(0x5E, "LSR", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
Add(0x2A, "ROL", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
Add(0x26, "ROL", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
Add(0x36, "ROL", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
Add(0x2E, "ROL", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
Add(0x3E, "ROL", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
Add(0x6A, "ROR", AddressingMode.Accumulator, 1, 2, false, InstructionType.Shift);
Add(0x66, "ROR", AddressingMode.ZeroPage, 2, 5, false, InstructionType.Shift);
Add(0x76, "ROR", AddressingMode.ZeroPageX, 2, 6, false, InstructionType.Shift);
Add(0x6E, "ROR", AddressingMode.Absolute, 3, 6, false, InstructionType.Shift);
Add(0x7E, "ROR", AddressingMode.AbsoluteX, 3, 7, false, InstructionType.Shift);
// Jumps & Calls
Add(0x4C, "JMP", AddressingMode.Absolute, 3, 3, false, InstructionType.Jump);
Add(0x6C, "JMP", AddressingMode.Indirect, 3, 5, false, InstructionType.Jump);
Add(0x20, "JSR", AddressingMode.Absolute, 3, 6, false, InstructionType.Jump);
Add(0x60, "RTS", AddressingMode.Implied, 1, 6, false, InstructionType.Return);
Add(0x40, "RTI", AddressingMode.Implied, 1, 6, false, InstructionType.Return);
// Branches
Add(0x90, "BCC", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0xB0, "BCS", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0xF0, "BEQ", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0x30, "BMI", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0xD0, "BNE", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0x10, "BPL", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0x50, "BVC", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
Add(0x70, "BVS", AddressingMode.Relative, 2, 2, true, InstructionType.Branch);
// Status Flag Changes
Add(0x18, "CLC", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
Add(0xD8, "CLD", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
Add(0x58, "CLI", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
Add(0xB8, "CLV", AddressingMode.Implied, 1, 2, false, InstructionType.Clear);
Add(0x38, "SEC", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
Add(0xF8, "SED", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
Add(0x78, "SEI", AddressingMode.Implied, 1, 2, false, InstructionType.Set);
// System Functions
Add(0x00, "BRK", AddressingMode.Implied, 1, 7, false, InstructionType.Interrupt);
Add(0xEA, "NOP", AddressingMode.Implied, 1, 2, false, InstructionType.Other);
// Unofficial/Illegal opcodes are added here if needed by further forks, however this should cover all of the NES 6502 instructions
}
/// <summary>
/// Adds an instruction to the instruction set
/// </summary>
private static void Add(byte opcode, string mnemonic, AddressingMode addressingMode,
byte size, byte cycles, bool extraCycleOnPageCross, InstructionType type)
{
var instruction = new InstructionInfo(
opcode, mnemonic, addressingMode, size, cycles, extraCycleOnPageCross, type);
instructions[opcode] = instruction;
mnemonicMap[(mnemonic, addressingMode)] = instruction;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,459 @@
using System;
using System.Collections.Generic;
using System.Text;
using NESDecompiler.Core.CPU;
using NESDecompiler.Core.Exceptions;
using NESDecompiler.Core.ROM;
namespace NESDecompiler.Core.Disassembly
{
/// <summary>
/// Represents a disassembled instruction with its address and operands
/// </summary>
public class DisassembledInstruction
{
/// <summary>
/// The address of this instruction in the ROM
/// </summary>
public ushort Address { get; set; }
/// <summary>
/// The CPU memory address this instruction maps to
/// </summary>
public ushort CPUAddress { get; set; }
/// <summary>
/// Information about this instruction's opcode
/// </summary>
public InstructionInfo Info { get; set; }
/// <summary>
/// The raw bytes of this instruction (including operands)
/// </summary>
public byte[] Bytes { get; set; }
/// <summary>
/// The operand bytes of this instruction
/// </summary>
public byte[] Operands => Bytes.Length > 1 ? Bytes[1..] : Array.Empty<byte>();
/// <summary>
/// The target address for branch and jump instructions
/// </summary>
public ushort? TargetAddress { get; set; }
/// <summary>
/// Potential label for this instruction
/// </summary>
public string? Label { get; set; }
/// <summary>
/// Potential comment for this instruction
/// </summary>
public string? Comment { get; set; }
/// <summary>
/// Whether this instruction is a potential function entry point
/// </summary>
public bool IsFunctionEntry { get; set; }
/// <summary>
/// Whether this instruction is a potential function exit point
/// </summary>
public bool IsFunctionExit => Info.Mnemonic == "RTS" || Info.Mnemonic == "RTI";
/// <summary>
/// Whether this instruction is a branch instruction
/// </summary>
public bool IsBranch => Info.Type == InstructionType.Branch;
/// <summary>
/// Whether this instruction is a jump instruction
/// </summary>
public bool IsJump => Info.Mnemonic == "JMP" || Info.Mnemonic == "JSR";
/// <summary>
/// Returns a string representation of this instruction
/// </summary>
public override string ToString()
{
var sb = new StringBuilder();
if (!string.IsNullOrEmpty(Label))
{
sb.AppendLine($"{Label}:");
}
sb.Append($"{CPUAddress:X4} ");
foreach (var b in Bytes)
{
sb.Append($"{b:X2} ");
}
sb.Append(new string(' ', (3 - Bytes.Length) * 3 + 2));
sb.Append(Info.Mnemonic);
if (Info.AddressingMode != AddressingMode.Implied &&
Info.AddressingMode != AddressingMode.Accumulator)
{
sb.Append(' ');
if (Info.AddressingMode == AddressingMode.Relative && TargetAddress.HasValue)
{
sb.Append($"${TargetAddress.Value:X4}");
}
else if (Operands.Length == 1)
{
string operandFormat = Info.GetOperandFormat();
sb.Append(string.Format(operandFormat, Operands[0]));
}
else if (Operands.Length == 2)
{
string operandFormat = Info.GetOperandFormat();
ushort value = (ushort)((Operands[1] << 8) | Operands[0]);
sb.Append(string.Format(operandFormat, value));
}
}
if (!string.IsNullOrEmpty(Comment))
{
sb.Append($" ; {Comment}");
}
return sb.ToString();
}
}
/// <summary>
/// Disassembles 6502 machine code into assembly language
/// </summary>
public class Disassembler
{
private ROMInfo romInfo;
private byte[] codeData;
private List<DisassembledInstruction> instructions;
private Dictionary<ushort, DisassembledInstruction> addressToInstruction;
private HashSet<ushort> entryPoints;
private HashSet<ushort> referencedAddresses;
private Dictionary<ushort, string> labels;
private int labelCounter;
/// <summary>
/// The list of disassembled instructions
/// </summary>
public IReadOnlyList<DisassembledInstruction> Instructions => instructions;
/// <summary>
/// Maps CPU addresses to disassembled instructions
/// </summary>
public IReadOnlyDictionary<ushort, DisassembledInstruction> AddressToInstruction => addressToInstruction;
/// <summary>
/// The list of entry points (e.g., reset vector, NMI vector)
/// </summary>
public IReadOnlySet<ushort> EntryPoints => entryPoints;
/// <summary>
/// The list of addresses referenced by the code
/// </summary>
public IReadOnlySet<ushort> ReferencedAddresses => referencedAddresses;
/// <summary>
/// Maps CPU addresses to labels
/// </summary>
public IReadOnlyDictionary<ushort, string> Labels => labels;
/// <summary>
/// Creates a new disassembler for the specified ROM
/// </summary>
/// <param name="romInfo">Information about the ROM</param>
/// <param name="codeData">The code data to disassemble</param>
public Disassembler(ROMInfo romInfo, byte[] codeData)
{
this.romInfo = romInfo ?? throw new ArgumentNullException(nameof(romInfo));
this.codeData = codeData ?? throw new ArgumentNullException(nameof(codeData));
instructions = new List<DisassembledInstruction>();
addressToInstruction = new Dictionary<ushort, DisassembledInstruction>();
entryPoints = new HashSet<ushort>();
referencedAddresses = new HashSet<ushort>();
labels = new Dictionary<ushort, string>();
labelCounter = 0;
if (romInfo.ResetVector != 0)
{
entryPoints.Add(romInfo.ResetVector);
}
}
/// <summary>
/// Disassembles the code data
/// </summary>
public void Disassemble()
{
LinearDisassembly();
TraceExecution();
IdentifyFunctions();
GenerateLabels();
}
/// <summary>
/// Gets the disassembled instruction at the specified address
/// </summary>
/// <param name="address">The CPU address</param>
/// <returns>The disassembled instruction, or null if not found</returns>
public DisassembledInstruction? GetInstructionAt(ushort address)
{
addressToInstruction.TryGetValue(address, out var instruction);
return instruction;
}
/// <summary>
/// Performs a linear disassembly of the code data
/// </summary>
private void LinearDisassembly()
{
try
{
int offset = 0;
ushort baseAddress = 0x8000;
while (offset < codeData.Length)
{
ushort cpuAddress = (ushort)(baseAddress + offset);
byte opcode = codeData[offset];
var instructionInfo = InstructionSet.GetInstruction(opcode);
if (!instructionInfo.IsValid)
{
offset++;
continue;
}
if (offset + instructionInfo.Size > codeData.Length)
{
offset++;
continue;
}
byte[] bytes = new byte[instructionInfo.Size];
Array.Copy(codeData, offset, bytes, 0, instructionInfo.Size);
var instruction = new DisassembledInstruction
{
Address = (ushort)offset,
CPUAddress = cpuAddress,
Info = instructionInfo,
Bytes = bytes
};
CalculateTargetAddress(instruction);
instructions.Add(instruction);
addressToInstruction[cpuAddress] = instruction;
offset += instructionInfo.Size;
}
}
catch (Exception ex)
{
throw new DisassemblyException($"Error during linear disassembly: {ex.Message}", ex);
}
}
/// <summary>
/// Traces execution from known entry points
/// </summary>
private void TraceExecution()
{
try
{
var toTrace = new Queue<ushort>(entryPoints);
var traced = new HashSet<ushort>();
while (toTrace.Count > 0)
{
ushort address = toTrace.Dequeue();
if (traced.Contains(address))
{
continue;
}
traced.Add(address);
if (!addressToInstruction.TryGetValue(address, out var instruction))
{
continue;
}
if (entryPoints.Contains(address))
{
instruction.IsFunctionEntry = true;
}
if (instruction.IsJump)
{
if (instruction.TargetAddress.HasValue)
{
ushort target = instruction.TargetAddress.Value;
referencedAddresses.Add(target);
if (instruction.Info.Mnemonic == "JSR")
{
entryPoints.Add(target);
ushort returnAddress = (ushort)(address + instruction.Info.Size);
toTrace.Enqueue(returnAddress);
}
toTrace.Enqueue(target);
if (instruction.Info.Mnemonic == "JMP")
{
continue;
}
}
}
else if (instruction.IsBranch)
{
if (instruction.TargetAddress.HasValue)
{
ushort target = instruction.TargetAddress.Value;
referencedAddresses.Add(target);
toTrace.Enqueue(target);
}
}
else if (instruction.IsFunctionExit)
{
continue;
}
ushort nextAddress = (ushort)(address + instruction.Info.Size);
toTrace.Enqueue(nextAddress);
}
}
catch (Exception ex)
{
throw new DisassemblyException($"Error during execution tracing: {ex.Message}", ex);
}
}
/// <summary>
/// Identifies functions and their boundaries
/// </summary>
private void IdentifyFunctions()
{
try
{
foreach (ushort entryPoint in entryPoints)
{
if (addressToInstruction.TryGetValue(entryPoint, out var instruction))
{
instruction.IsFunctionEntry = true;
}
}
}
catch (Exception ex)
{
throw new DisassemblyException($"Error during function identification: {ex.Message}", ex);
}
}
/// <summary>
/// Generates labels for referenced addresses
/// </summary>
private void GenerateLabels()
{
try
{
foreach (ushort entryPoint in entryPoints)
{
if (addressToInstruction.TryGetValue(entryPoint, out var instruction))
{
string label = $"sub_{entryPoint:X4}";
instruction.Label = label;
labels[entryPoint] = label;
}
}
foreach (ushort address in referencedAddresses)
{
if (!labels.ContainsKey(address) && addressToInstruction.TryGetValue(address, out var instruction))
{
string label = $"loc_{labelCounter++:X4}";
instruction.Label = label;
labels[address] = label;
}
}
foreach (var instruction in instructions)
{
if (instruction.TargetAddress.HasValue)
{
ushort target = instruction.TargetAddress.Value;
if (labels.TryGetValue(target, out string label))
{
instruction.Comment = $"-> {label}";
}
}
}
}
catch (Exception ex)
{
throw new DisassemblyException($"Error during label generation: {ex.Message}", ex);
}
}
/// <summary>
/// Calculates the target address for branch and jump instructions
/// </summary>
/// <param name="instruction">The instruction to process</param>
private void CalculateTargetAddress(DisassembledInstruction instruction)
{
if (instruction.Info.AddressingMode == AddressingMode.Relative)
{
// Branch instructions use relative addressing
// The offset is signed and relative to the next instruction
sbyte offset = (sbyte)instruction.Operands[0];
ushort nextAddress = (ushort)(instruction.CPUAddress + instruction.Info.Size);
instruction.TargetAddress = (ushort)(nextAddress + offset);
}
else if (instruction.IsJump &&
(instruction.Info.AddressingMode == AddressingMode.Absolute ||
instruction.Info.AddressingMode == AddressingMode.Indirect))
{
if (instruction.Operands.Length == 2)
{
ushort target = (ushort)((instruction.Operands[1] << 8) | instruction.Operands[0]);
instruction.TargetAddress = target;
}
}
}
/// <summary>
/// Returns the disassembly as a formatted string
/// </summary>
public string ToAssemblyString()
{
var sb = new StringBuilder();
sb.AppendLine("; 6502 Disassembly");
sb.AppendLine($"; ROM: {romInfo}");
sb.AppendLine();
foreach (var instruction in instructions)
{
sb.AppendLine(instruction.ToString());
}
return sb.ToString();
}
}
}
@@ -0,0 +1,64 @@
using System;
namespace NESDecompiler.Core.Exceptions
{
/// <summary>
/// Base exception class for ROM-related errors
/// </summary>
public class ROMException : Exception
{
public ROMException(string message) : base(message)
{
}
public ROMException(string message, Exception innerException)
: base(message, innerException)
{
}
}
/// <summary>
/// Exception thrown when a ROM has an invalid format
/// </summary>
public class InvalidROMFormatException : ROMException
{
public InvalidROMFormatException(string message) : base(message)
{
}
public InvalidROMFormatException(string message, Exception innerException)
: base(message, innerException)
{
}
}
/// <summary>
/// Exception thrown during the disassembly process
/// </summary>
public class DisassemblyException : Exception
{
public DisassemblyException(string message) : base(message)
{
}
public DisassemblyException(string message, Exception innerException)
: base(message, innerException)
{
}
}
/// <summary>
/// Exception thrown during the decompilation process
/// </summary>
public class DecompilationException : Exception
{
public DecompilationException(string message) : base(message)
{
}
public DecompilationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+100
View File
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
namespace NESDecompiler.Core.ROM
{
/// <summary>
/// Mirroring types for NES hardware
/// </summary>
public enum MirroringType
{
Horizontal,
Vertical,
FourScreen
}
/// <summary>
/// Contains information about a loaded NES ROM
/// </summary>
public class ROMInfo
{
/// <summary>
/// Size of the PRG ROM in bytes
/// </summary>
public int PRGROMSize { get; set; }
/// <summary>
/// Size of the CHR ROM in bytes
/// </summary>
public int CHRROMSize { get; set; }
/// <summary>
/// Mapper number
/// </summary>
public byte MapperNumber { get; set; }
/// <summary>
/// Type of mirroring used
/// </summary>
public MirroringType MirroringType { get; set; }
/// <summary>
/// Whether the ROM has battery-backed RAM
/// </summary>
public bool HasBatteryBackedRAM { get; set; }
/// <summary>
/// Whether the ROM has a trainer
/// </summary>
public bool HasTrainer { get; set; }
/// <summary>
/// Whether the ROM uses four-screen VRAM
/// </summary>
public bool HasFourScreenVRAM { get; set; }
/// <summary>
/// Whether the ROM is a VS System cartridge
/// </summary>
public bool IsVSSystemCart { get; set; }
/// <summary>
/// Offset of the PRG ROM in the file
/// </summary>
public int PRGROMOffset { get; set; }
/// <summary>
/// Offset of the CHR ROM in the file
/// </summary>
public int CHRROMOffset { get; set; }
/// <summary>
/// The Reset Vector (entry point) address
/// </summary>
public ushort ResetVector { get; set; }
/// <summary>
/// The raw ROM data for reference
/// </summary>
public byte[] RawData { get; set; }
/// <summary>
/// List of identified entry points (including reset vector and NMI)
/// </summary>
public HashSet<ushort> EntryPoints { get; } = new HashSet<ushort>();
/// <summary>
/// Returns a string representation of the ROM information
/// </summary>
public override string ToString()
{
return $"ROM Info:\n" +
$" PRG ROM: {PRGROMSize} bytes\n" +
$" CHR ROM: {CHRROMSize} bytes\n" +
$" Mapper: {MapperNumber}\n" +
$" Mirroring: {MirroringType}\n" +
$" Battery-Backed RAM: {HasBatteryBackedRAM}\n" +
$" Reset Vector: ${ResetVector:X4}";
}
}
}
+171
View File
@@ -0,0 +1,171 @@
using System;
using System.IO;
using System.Text;
using NESDecompiler.Core.Exceptions;
namespace NESDecompiler.Core.ROM
{
/// <summary>
/// Handles loading and parsing NES ROM files
/// </summary>
public class ROMLoader
{
// iNES header constants
private const int HEADER_SIZE = 16;
private const int PRG_ROM_SIZE_OFFSET = 4;
private const int CHR_ROM_SIZE_OFFSET = 5;
private const int FLAGS_6_OFFSET = 6;
private const int FLAGS_7_OFFSET = 7;
private const int PRG_RAM_SIZE_OFFSET = 8;
private const int FLAGS_9_OFFSET = 9;
private const int FLAGS_10_OFFSET = 10;
// ROM data
private byte[] romData;
private ROMInfo romInfo;
/// <summary>
/// Information about the loaded ROM
/// </summary>
public ROMInfo ROMInfo => romInfo;
/// <summary>
/// Loads a NES ROM file from disk
/// </summary>
/// <param name="filePath">Path to the ROM file</param>
/// <returns>Information about the loaded ROM</returns>
public ROMInfo LoadFromFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException("ROM file not found", filePath);
}
try
{
romData = File.ReadAllBytes(filePath);
return ParseROMHeader();
}
catch (Exception ex) when (ex is not ROMException)
{
throw new ROMException($"Failed to load ROM file: {ex.Message}", ex);
}
}
/// <summary>
/// Loads a NES ROM from a byte array
/// </summary>
/// <param name="data">The ROM data</param>
/// <returns>Information about the loaded ROM</returns>
public ROMInfo LoadFromBytes(byte[] data)
{
if (data == null || data.Length < HEADER_SIZE)
{
throw new ROMException("Invalid ROM data: too short");
}
romData = data;
return ParseROMHeader();
}
/// <summary>
/// Parses the iNES ROM header
/// </summary>
/// <returns>Information about the ROM</returns>
private ROMInfo ParseROMHeader()
{
// Verify iNES header signature "NES" followed by MS-DOS EOF
if (romData.Length < HEADER_SIZE ||
romData[0] != 0x4E || romData[1] != 0x45 || romData[2] != 0x53 || romData[3] != 0x1A)
{
throw new InvalidROMFormatException("Invalid iNES ROM header");
}
romInfo = new ROMInfo
{
PRGROMSize = romData[PRG_ROM_SIZE_OFFSET] * 16384, // 16KB units
CHRROMSize = romData[CHR_ROM_SIZE_OFFSET] * 8192, // 8KB units
MapperNumber = (byte)((romData[FLAGS_7_OFFSET] & 0xF0) | ((romData[FLAGS_6_OFFSET] & 0xF0) >> 4)),
MirroringType = (romData[FLAGS_6_OFFSET] & 0x01) == 0
? MirroringType.Horizontal
: MirroringType.Vertical,
HasBatteryBackedRAM = (romData[FLAGS_6_OFFSET] & 0x02) != 0,
HasTrainer = (romData[FLAGS_6_OFFSET] & 0x04) != 0,
HasFourScreenVRAM = (romData[FLAGS_6_OFFSET] & 0x08) != 0,
IsVSSystemCart = (romData[FLAGS_7_OFFSET] & 0x01) != 0,
PRGROMOffset = HEADER_SIZE + (((romData[FLAGS_6_OFFSET] & 0x04) != 0) ? 512 : 0),
RawData = romData
};
romInfo.CHRROMOffset = romInfo.PRGROMOffset + romInfo.PRGROMSize;
// Identify entry points (reset vector)
if (romInfo.PRGROMSize > 0)
{
// In 6502, reset vector is at 0xFFFC-0xFFFD
// For NES, this is mapped to the end of the first PRG ROM bank
int resetVectorOffset = romInfo.PRGROMOffset + romInfo.PRGROMSize - 4;
if (resetVectorOffset >= 0 && resetVectorOffset < romData.Length - 1)
{
romInfo.ResetVector = (ushort)(romData[resetVectorOffset] | (romData[resetVectorOffset + 1] << 8));
}
}
return romInfo;
}
/// <summary>
/// Gets a segment of the ROM data
/// </summary>
/// <param name="offset">Starting offset</param>
/// <param name="length">Number of bytes to read</param>
/// <returns>The requested data segment</returns>
public byte[] GetROMSegment(int offset, int length)
{
if (romData == null)
throw new ROMException("No ROM data loaded");
if (offset < 0 || offset + length > romData.Length)
throw new ROMException("Requested segment is out of bounds");
byte[] segment = new byte[length];
Array.Copy(romData, offset, segment, 0, length);
return segment;
}
/// <summary>
/// Gets the PRG ROM data
/// </summary>
/// <returns>The PRG ROM data</returns>
public byte[] GetPRGROMData()
{
if (romInfo == null)
throw new ROMException("No ROM data loaded");
return GetROMSegment(romInfo.PRGROMOffset, romInfo.PRGROMSize);
}
/// <summary>
/// Gets the CHR ROM data
/// </summary>
/// <returns>The CHR ROM data</returns>
public byte[] GetCHRROMData()
{
if (romInfo == null)
throw new ROMException("No ROM data loaded");
if (romInfo.CHRROMSize == 0)
return Array.Empty<byte>();
return GetROMSegment(romInfo.CHRROMOffset, romInfo.CHRROMSize);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
<Application x:Class="NESDecompiler.GUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NESDecompiler.GUI"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Themes/ThemeManager.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
+14
View File
@@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace NESDecompiler.GUI
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
+82
View File
@@ -0,0 +1,82 @@
using System;
using System.Windows;
using Microsoft.Xaml.Behaviors;
using ICSharpCode.AvalonEdit;
namespace NESDecompiler.GUI
{
/// <summary>
/// Behavior for binding the text of an AvalonEdit control
/// </summary>
public class AvalonEditBehavior : Behavior<TextEditor>
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(AvalonEditBehavior),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback));
/// <summary>
/// The bound text property
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Called when the Text property changes
/// </summary>
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as AvalonEditBehavior;
if (behavior != null && behavior.AssociatedObject != null)
{
var editor = behavior.AssociatedObject;
if (editor.Text != e.NewValue as string)
{
editor.Text = e.NewValue as string ?? string.Empty;
}
}
}
/// <summary>
/// Called when the behavior is attached to the control
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject != null)
{
AssociatedObject.Text = Text ?? string.Empty;
AssociatedObject.TextChanged += OnTextChanged;
}
}
/// <summary>
/// Called when the behavior is detached from the control
/// </summary>
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.TextChanged -= OnTextChanged;
}
base.OnDetaching();
}
/// <summary>
/// Called when the text in the editor changes
/// </summary>
private void OnTextChanged(object? sender, EventArgs e)
{
if (AssociatedObject != null)
{
Text = AssociatedObject.Text;
}
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.Windows;
using ICSharpCode.AvalonEdit;
namespace NESDecompiler.GUI
{
/// <summary>
/// Provides attached properties for binding text to AvalonEdit
/// </summary>
public static class AvalonEditTextBindingBehavior
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(AvalonEditTextBindingBehavior),
new FrameworkPropertyMetadata(
default(string),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnTextChanged));
/// <summary>
/// Gets the text value
/// </summary>
public static string GetText(DependencyObject obj)
{
return (string)obj.GetValue(TextProperty);
}
/// <summary>
/// Sets the text value
/// </summary>
public static void SetText(DependencyObject obj, string value)
{
obj.SetValue(TextProperty, value);
}
/// <summary>
/// Called when the text property changes
/// </summary>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextEditor editor)
{
if (editor.Text != (string)e.NewValue)
{
editor.Text = (string)e.NewValue ?? string.Empty;
}
if (e.OldValue != null)
{
editor.TextChanged -= Editor_TextChanged;
}
if (e.NewValue != null)
{
editor.TextChanged += Editor_TextChanged;
}
}
}
/// <summary>
/// Called when the editor text changes
/// </summary>
private static void Editor_TextChanged(object sender, EventArgs e)
{
if (sender is TextEditor editor)
{
SetText(editor, editor.Text);
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
using System;
using System.Windows.Input;
namespace NESDecompiler.GUI.Commands
{
/// <summary>
/// A command whose sole purpose is to relay its functionality to other
/// objects by invoking delegates.
/// </summary>
public class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool>? canExecute;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool>? canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
/// <summary>
/// Determines whether this command can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object? parameter)
{
return canExecute == null || canExecute();
}
/// <summary>
/// Executes the command on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public void Execute(object? parameter)
{
execute();
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Raises the CanExecuteChanged event.
/// </summary>
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
/// <summary>
/// A generic command whose sole purpose is to relay its functionality to other
/// objects by invoking delegates.
/// </summary>
/// <typeparam name="T">The type of parameter that this command expects.</typeparam>
public class RelayCommand<T> : ICommand
{
private readonly Action<T?> execute;
private readonly Predicate<T?>? canExecute;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T?> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T?> execute, Predicate<T?>? canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
/// <summary>
/// Determines whether this command can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object? parameter)
{
return canExecute == null || canExecute((T?)parameter);
}
/// <summary>
/// Executes the command on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public void Execute(object? parameter)
{
execute((T?)parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Raises the CanExecuteChanged event.
/// </summary>
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}
+23
View File
@@ -0,0 +1,23 @@
<Window x:Class="NESDecompiler.GUI.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Input" Height="150" Width="400"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False"
ResizeMode="NoResize">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PromptText" Grid.Row="0" Text="Enter value:" Margin="0,0,0,10"/>
<TextBox x:Name="ResponseTextBox" Grid.Row="1" Margin="0,0,0,10"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="OK" Width="75" Click="OKButton_Click" IsDefault="True" Margin="0,0,5,0"/>
<Button Content="Cancel" Width="75" Click="CancelButton_Click" IsCancel="True"/>
</StackPanel>
</Grid>
</Window>
+35
View File
@@ -0,0 +1,35 @@
using System.Windows;
namespace NESDecompiler.GUI
{
public partial class InputDialog : Window
{
public string ResponseText
{
get { return ResponseTextBox.Text; }
set { ResponseTextBox.Text = value; }
}
public InputDialog(string title, string prompt, string defaultResponse = "")
{
InitializeComponent();
Title = title;
PromptText.Text = prompt;
ResponseText = defaultResponse;
ResponseTextBox.SelectAll();
ResponseTextBox.Focus();
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
+358
View File
@@ -0,0 +1,358 @@
<Window x:Class="NESDecompiler.GUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NESDecompiler.GUI"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
mc:Ignorable="d"
Title="NES Decompiler" Height="800" Width="1200">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="_File">
<MenuItem Header="_Open ROM..." Command="{Binding OpenROMCommand}"/>
<Separator/>
<MenuItem Header="_Save Disassembly..." Command="{Binding SaveDisassemblyCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="Save _C Code..." Command="{Binding SaveCCodeCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="Save _Header..." Command="{Binding SaveHeaderCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="Save to _Workspace..." Command="{Binding SaveWorkspaceCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="E_xport to VS2022..." Command="{Binding ExportToVSCommand}" IsEnabled="{Binding IsDecompiled}"/>
<Separator/>
<MenuItem Header="E_xit" Command="{Binding ExitCommand}"/>
</MenuItem>
<MenuItem Header="_Project">
<MenuItem Header="_New Project" Command="{Binding NewProjectCommand}"/>
<MenuItem Header="_Open Project..." Command="{Binding OpenProjectCommand}"/>
<MenuItem Header="_Save Project" Command="{Binding SaveProjectCommand}" IsEnabled="{Binding HasProject}"/>
<MenuItem Header="Save Project _As..." Command="{Binding SaveProjectAsCommand}" IsEnabled="{Binding HasProject}"/>
<Separator/>
<MenuItem Header="Add _ROM to Project..." Command="{Binding AddROMCommand}" IsEnabled="{Binding HasProject}"/>
<MenuItem Header="_Remove Active ROM" Command="{Binding RemoveROMCommand}" IsEnabled="{Binding IsROMLoaded}"/>
</MenuItem>
<MenuItem Header="_Actions">
<MenuItem Header="_Disassemble" Command="{Binding DisassembleCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="_Decompile" Command="{Binding DecompileCommand}" IsEnabled="{Binding IsDisassembled}"/>
</MenuItem>
<MenuItem Header="_View">
<MenuItem Header="ROM _Info" Command="{Binding ViewROMInfoCommand}" IsEnabled="{Binding IsROMLoaded}"/>
<MenuItem Header="_Variables" Command="{Binding ViewVariablesCommand}" IsEnabled="{Binding IsDecompiled}"/>
<MenuItem Header="_Functions" Command="{Binding ViewFunctionsCommand}" IsEnabled="{Binding IsDecompiled}"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About" Command="{Binding AboutCommand}"/>
</MenuItem>
</Menu>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Project Explorer" Margin="5" FontWeight="Bold"/>
<TreeView Grid.Row="1" ItemsSource="{Binding ProjectItems}"
SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}" Width="16" Height="16" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
<!--Style="{StaticResource DarkModeToggleSwitch}"-->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="1" x:Name="mainTabControl">
<TabItem Header="Welcome" IsSelected="True" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}">
<Grid Background="{DynamicResource SecondaryBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="NES Decompiler" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<TextBlock Grid.Row="1" Text="Static Decompiler for NES/6502 ROMs" FontSize="16" Margin="0,10,0,30" HorizontalAlignment="Center" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<!-- Theme Toggle -->
<!--<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
<TextBlock Text="Light" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<ToggleButton
IsChecked="{Binding IsDarkTheme}"
Command="{Binding ToggleThemeCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
VerticalAlignment="Center"/>
<TextBlock Text="Dark" VerticalAlignment="Center" Margin="10,0,0,0" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
</StackPanel>-->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
<Button Content="New Project" Width="150" Height="40" Command="{Binding NewProjectCommand}" Margin="0,0,20,0" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<Button Content="Open Project..." Width="150" Height="40" Command="{Binding OpenProjectCommand}" Margin="0,0,20,0" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<Button Content="Open ROM..." Width="150" Height="40" Command="{Binding OpenROMCommand}" Background="{DynamicResource TertiaryBackgroundBrush}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
</StackPanel>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="10">
<TextBlock Text="Recent Projects:" FontWeight="Bold" Margin="0,0,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<ListView Height="200" Width="350" ItemsSource="{Binding RecentProjectsList}"
MouseDoubleClick="RecentProjects_MouseDoubleClick"
Background="{DynamicResource TertiaryBackgroundBrush}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
BorderBrush="{DynamicResource BorderBrush}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<TextBlock Text="{Binding FilePath}" FontSize="10" Foreground="{DynamicResource SecondaryForegroundBrush}"/>
<TextBlock Text="{Binding LastOpened, StringFormat='Last opened: {0}'}" FontSize="10" Foreground="{DynamicResource SecondaryForegroundBrush}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<StackPanel Grid.Column="1" Margin="10">
<TextBlock Text="Recent Workspaces:" FontWeight="Bold" Margin="0,0,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<ListView Height="200" Width="350" ItemsSource="{Binding RecentWorkspaces}"
MouseDoubleClick="RecentWorkspaces_MouseDoubleClick"
Background="{DynamicResource TertiaryBackgroundBrush}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
BorderBrush="{DynamicResource BorderBrush}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
<TextBlock Grid.Row="5" Text="Recent Files:" FontWeight="Bold" Margin="10,20,0,10" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
<ListBox Grid.Row="5" ItemsSource="{Binding RecentFiles}" Height="100" Margin="10,50,10,10"
MouseDoubleClick="RecentFiles_MouseDoubleClick"
Background="{DynamicResource TertiaryBackgroundBrush}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
BorderBrush="{DynamicResource BorderBrush}"/>
</Grid>
</TabItem>
<TabItem Header="Disassembly" Visibility="{Binding IsDisassembled, Converter={StaticResource BoolToVis}}">
<Grid>
<avalonEdit:TextEditor x:Name="disassemblyEditor"
FontFamily="Consolas"
FontSize="12"
IsReadOnly="True"
ShowLineNumbers="True"
WordWrap="False">
</avalonEdit:TextEditor>
</Grid>
</TabItem>
<TabItem Header="C Code" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
<Grid>
<avalonEdit:TextEditor x:Name="cCodeEditor"
FontFamily="Consolas"
FontSize="12"
IsReadOnly="True"
ShowLineNumbers="True"
WordWrap="False">
</avalonEdit:TextEditor>
</Grid>
</TabItem>
<TabItem Header="Header" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
<Grid>
<avalonEdit:TextEditor x:Name="headerEditor"
FontFamily="Consolas"
FontSize="12"
IsReadOnly="True"
ShowLineNumbers="True"
WordWrap="False">
</avalonEdit:TextEditor>
</Grid>
</TabItem>
<TabItem Header="Variables" Name="VariablesTab" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBar Grid.Row="0">
<Button Content="Refresh" Command="{Binding ViewVariablesCommand}"/>
<Separator/>
<Grid Width="200" Margin="5,0">
<TextBox Text="{Binding FunctionSearchText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock IsHitTestVisible="False" Text="Search functions..." Foreground="Gray" Margin="5,0,0,0"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding FunctionSearchText}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Button Content="Search" Command="{Binding SearchVariablesCommand}"/>
</ToolBar>
<DataGrid Grid.Row="1"
ItemsSource="{Binding VariableList}"
SelectedItem="{Binding SelectedVariable}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserSortColumns="True"
Background="{DynamicResource SecondaryBackgroundBrush}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
BorderBrush="{DynamicResource BorderBrush}"
RowBackground="{DynamicResource SecondaryBackgroundBrush}"
AlternatingRowBackground="{DynamicResource TertiaryBackgroundBrush}"
HorizontalGridLinesBrush="{DynamicResource BorderBrush}"
VerticalGridLinesBrush="{DynamicResource BorderBrush}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Jump to Definition" Command="{Binding JumpToVariableDefinitionCommand}"/>
<MenuItem Header="Find All References" Command="{Binding FindVariableReferencesCommand}"/>
<Separator/>
<MenuItem Header="Rename Variable" Command="{Binding RenameVariableCommand}"/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="Address" Binding="{Binding AddressHex}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="False"/>
<DataGridTextColumn Header="Type" Binding="{Binding TypeName}" IsReadOnly="False"/>
<DataGridTextColumn Header="Size" Binding="{Binding Size}" IsReadOnly="True"/>
<DataGridTextColumn Header="Access" Binding="{Binding AccessType}" IsReadOnly="True"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="False" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</TabItem>
<TabItem Header="Functions" Name="FunctionsTab" Visibility="{Binding IsDecompiled, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBar Grid.Row="0">
<Button Content="Refresh" Command="{Binding ViewFunctionsCommand}"/>
<Separator/>
<Grid Width="200" Margin="5,0">
<TextBox Text="{Binding VariableSearchText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock IsHitTestVisible="False" Text="Search variables..." Foreground="Gray" Margin="5,0,0,0"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding VariableSearchText}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Button Content="Search" Command="{Binding SearchFunctionsCommand}"/>
</ToolBar>
<DataGrid Grid.Row="1"
ItemsSource="{Binding FunctionList}"
SelectedItem="{Binding SelectedFunction}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserSortColumns="True"
Background="{DynamicResource SecondaryBackgroundBrush}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
BorderBrush="{DynamicResource BorderBrush}"
RowBackground="{DynamicResource SecondaryBackgroundBrush}"
AlternatingRowBackground="{DynamicResource TertiaryBackgroundBrush}"
HorizontalGridLinesBrush="{DynamicResource BorderBrush}"
VerticalGridLinesBrush="{DynamicResource BorderBrush}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Jump to Definition" Command="{Binding JumpToFunctionDefinitionCommand}"/>
<MenuItem Header="Find All References" Command="{Binding FindFunctionReferencesCommand}"/>
<Separator/>
<MenuItem Header="Rename Function" Command="{Binding RenameFunctionCommand}"/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="Address" Binding="{Binding AddressHex}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="False"/>
<DataGridTextColumn Header="Instructions" Binding="{Binding InstructionCount}" IsReadOnly="True"/>
<DataGridTextColumn Header="Variables" Binding="{Binding VariableCount}" IsReadOnly="True"/>
<DataGridTextColumn Header="Called Functions" Binding="{Binding CalledFunctionCount}" IsReadOnly="True"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" IsReadOnly="False" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Grid>
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock Text="{Binding StatusText}"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<ProgressBar Width="100" Height="15" IsIndeterminate="{Binding IsProcessing}"/>
</StatusBarItem>
</StatusBar>
<Grid Grid.Row="1" Background="#80000000"
Visibility="{Binding IsProcessing, Converter={StaticResource BoolToVis}}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding ProcessingText}" Foreground="White" FontSize="16" Margin="0,0,0,10"/>
<ProgressBar Width="300" Height="20" IsIndeterminate="True"/>
</StackPanel>
</Grid>
</Grid>
</Window>
+305
View File
@@ -0,0 +1,305 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.ComponentModel;
using NESDecompiler.GUI.ViewModels;
using ICSharpCode.AvalonEdit.Highlighting;
using System.Xml;
using System.IO;
using System.Reflection;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using static NESDecompiler.GUI.ViewModels.ProjectItemViewModel;
using System.Windows.Media;
using NESDecompiler.GUI.Themes;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit;
namespace NESDecompiler.GUI
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new MainViewModel();
DataContext = viewModel;
LoadSyntaxHighlighting();
Loaded += MainWindow_Loaded;
Closing += MainWindow_Closing;
}
/// <summary>
/// Loads custom syntax highlighting for ASM 6502
/// </summary>
private void LoadSyntaxHighlighting()
{
try
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("NESDecompiler.GUI.Resources.ASM6502.xshd"))
{
if (stream != null)
{
using (var reader = new XmlTextReader(stream))
{
var highlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
HighlightingManager.Instance.RegisterHighlighting("ASM6502", new[] { ".asm" }, highlighting);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Failed to load syntax highlighting: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
viewModel.Initialize();
if (viewModel is INotifyPropertyChanged notifyViewModel)
{
notifyViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
cCodeEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
headerEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
try
{
disassemblyEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("ASM6502");
}
catch (Exception)
{
disassemblyEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
}
ThemeManager.Instance.ThemeChanged += (s, args) => ApplyThemeToEditors();
ApplyThemeToEditors();
SetupEditorEvents();
}
private void ApplyThemeToEditors()
{
bool isDarkTheme = ThemeManager.Instance.IsDarkTheme;
SolidColorBrush backgroundBrush = Application.Current.Resources["SecondaryBackgroundBrush"] as SolidColorBrush;
SolidColorBrush foregroundBrush = Application.Current.Resources["PrimaryForegroundBrush"] as SolidColorBrush;
if (backgroundBrush != null && foregroundBrush != null)
{
ApplyThemeToEditor(disassemblyEditor, backgroundBrush, foregroundBrush);
ApplyThemeToEditor(cCodeEditor, backgroundBrush, foregroundBrush);
ApplyThemeToEditor(headerEditor, backgroundBrush, foregroundBrush);
}
}
private void ApplyThemeToEditor(TextEditor editor, SolidColorBrush background, SolidColorBrush foreground)
{
if (editor != null)
{
editor.Background = background;
editor.Foreground = foreground;
}
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MainViewModel.DisassemblyText))
{
disassemblyEditor.Text = viewModel.DisassemblyText;
}
else if (e.PropertyName == nameof(MainViewModel.CCodeText))
{
cCodeEditor.Text = viewModel.CCodeText;
}
else if (e.PropertyName == nameof(MainViewModel.HeaderText))
{
headerEditor.Text = viewModel.HeaderText;
}
}
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
if (viewModel is INotifyPropertyChanged notifyViewModel)
{
notifyViewModel.PropertyChanged -= ViewModel_PropertyChanged;
}
if (viewModel.HasUnsavedChanges)
{
var result = MessageBox.Show(
"You have unsaved changes. Do you want to save before exiting?",
"Save Changes",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
if (result == MessageBoxResult.Cancel)
{
e.Cancel = true;
return;
}
if (result == MessageBoxResult.Yes)
{
viewModel.SaveAllCommand.Execute(null);
}
}
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var selectedItem = e.NewValue as ProjectItemViewModel;
var previousSelection = viewModel.SelectedProjectItem;
viewModel.SelectedProjectItem = selectedItem;
if (selectedItem != null)
{
if (sender is TreeView treeView)
{
if (selectedItem.Tag is ROMProject currentRom && previousSelection?.Tag is ROMProject prevRom)
{
currentRom.IsExpanded = true;
prevRom.IsExpanded = true;
KeepTreeViewItemsExpanded(treeView);
}
}
if (selectedItem.Tag is ROMProject selectedRom)
{
if (viewModel.ActiveROM != selectedRom)
{
viewModel.ActiveROM = selectedRom;
}
}
switch (selectedItem.Type)
{
case ProjectItemType.Disassembly:
ActivateTab("Disassembly");
break;
case ProjectItemType.CCode:
ActivateTab("C Code");
break;
case ProjectItemType.Header:
ActivateTab("Header");
break;
case ProjectItemType.Variables:
viewModel.ViewVariablesCommand.Execute(null);
break;
case ProjectItemType.Functions:
viewModel.ViewFunctionsCommand.Execute(null);
break;
case ProjectItemType.ROMInfo:
viewModel.ViewROMInfoCommand.Execute(null);
break;
default:
break;
}
}
}
private void KeepTreeViewItemsExpanded(TreeView treeView)
{
var expandedItems = FindExpandedItems(treeView);
foreach (var item in expandedItems)
{
item.IsExpanded = true;
}
}
private List<TreeViewItem> FindExpandedItems(DependencyObject parent)
{
var result = new List<TreeViewItem>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is TreeViewItem item && item.IsExpanded)
{
result.Add(item);
}
result.AddRange(FindExpandedItems(child));
}
return result;
}
private void RecentFiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListBox listBox && listBox.SelectedItem is string filePath)
{
viewModel.OpenRecentFile(filePath);
}
}
private void RecentProjects_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListView listView && listView.SelectedItem is RecentProjectInfo projectInfo)
{
viewModel.OpenRecentProject(projectInfo);
}
}
private void RecentWorkspaces_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListView listView && listView.SelectedItem is string filePath)
{
viewModel.OpenRecentWorkspace(filePath);
}
}
public void ActivateTab(string tabName)
{
foreach (TabItem tab in mainTabControl.Items)
{
if (tab.Name == tabName + "Tab" || tab.Header.ToString() == tabName)
{
tab.IsSelected = true;
return;
}
}
}
/// <summary>
/// Set up text change event handlers for editor controls
/// </summary>
private void SetupEditorEvents()
{
disassemblyEditor.TextChanged += (s, e) => {
if (viewModel.DisassemblyText != disassemblyEditor.Text)
{
viewModel.DisassemblyText = disassemblyEditor.Text;
}
};
cCodeEditor.TextChanged += (s, e) => {
if (viewModel.CCodeText != cCodeEditor.Text)
{
viewModel.CCodeText = cCodeEditor.Text;
}
};
headerEditor.TextChanged += (s, e) => {
if (viewModel.HeaderText != headerEditor.Text)
{
viewModel.HeaderText = headerEditor.Text;
}
};
}
}
}
+25
View File
@@ -0,0 +1,25 @@
using NESDecompiler.Core.Decompilation;
using NESDecompiler.Core.ROM;
public class Project
{
public string Name { get; set; }
public string FilePath { get; set; }
public List<ROMProject> ROMs { get; set; } = new List<ROMProject>();
public DateTime LastModified { get; set; }
}
public class ROMProject
{
public string Name { get; set; }
public string FilePath { get; set; }
public bool IsDisassembled { get; set; }
public bool IsDecompiled { get; set; }
public ROMInfo ROMInfo { get; set; }
public string DisassemblyText { get; set; } = string.Empty;
public string CCodeText { get; set; } = string.Empty;
public string HeaderText { get; set; } = string.Empty;
public Dictionary<string, VariableWorkspaceData> Variables { get; set; } = new Dictionary<string, VariableWorkspaceData>();
public Dictionary<string, FunctionWorkspaceData> Functions { get; set; } = new Dictionary<string, FunctionWorkspaceData>();
public bool IsExpanded { get; set; } = true;
}
@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\ASM6502.xshd" />
</ItemGroup>
<ItemGroup>
<Page Remove="Resources\Themes\DarkTheme.xaml" />
<Page Remove="Resources\Themes\LightTheme.xaml" />
<Page Remove="Resources\Themes\ThemeManager.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AvalonEdit" Version="6.2.0.78" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NESDecompiler.Core\NESDecompiler.Core.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\ASM6502.xshd">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Themes\DarkTheme.xaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\Themes\LightTheme.xaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\Themes\ThemeManager.xaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
</ItemGroup>
</Project>
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<SyntaxDefinition name="ASM6502" extensions=".asm" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="#008000" />
<Color name="String" foreground="#A31515" />
<Color name="Directive" foreground="#00FCF8" fontWeight="bold" />
<Color name="Instruction" foreground="#009BFC" />
<Color name="Register" foreground="#800080" />
<Color name="Label" foreground="#2B91AF" />
<Color name="Number" foreground="#FF8000" />
<Color name="Address" foreground="#FF0000" />
<RuleSet>
<!-- Comments -->
<Span color="Comment" begin=";" />
<Span color="Comment" begin="//" />
<Span color="Comment" multiline="true" begin="/\*" end="\*/" />
<!-- Strings -->
<Span color="String" begin="&quot;" end="&quot;" />
<Span color="String" begin="'" end="'" />
<!-- Directives -->
<Rule color="Directive">
\.(org|byte|word|db|dw|data|text|ascii|asciiz|define|include|macro|endm|segment|end)
</Rule>
<!-- Instructions -->
<Rule color="Instruction">
\b(adc|and|asl|bcc|bcs|beq|bit|bmi|bne|bpl|brk|bvc|bvs|clc|cld|cli|clv|cmp|cpx|cpy|dec|dex|dey|eor|inc|inx|iny|jmp|jsr|lda|ldx|ldy|lsr|nop|ora|pha|php|pla|plp|rol|ror|rti|rts|sbc|sec|sed|sei|sta|stx|sty|tax|tay|tsx|txa|txs|tya)\b
</Rule>
<!-- Registers -->
<Rule color="Register">
\b(a|x|y|sp)\b
</Rule>
<!-- Labels -->
<Rule color="Label">
^[a-zA-Z_][a-zA-Z0-9_]*:
</Rule>
<!-- Numbers -->
<Rule color="Number">
\b([$%]?[0-9A-Fa-f]+)\b
</Rule>
<!-- Addresses -->
<Rule color="Address">
\$[0-9A-Fa-f]+
</Rule>
</RuleSet>
</SyntaxDefinition>
@@ -0,0 +1,236 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Base Colors -->
<Color x:Key="PrimaryBackgroundColor">#1E1E1E</Color>
<Color x:Key="SecondaryBackgroundColor">#252526</Color>
<Color x:Key="TertiaryBackgroundColor">#2D2D30</Color>
<Color x:Key="PrimaryForegroundColor">#FFFFFF</Color>
<Color x:Key="SecondaryForegroundColor">#B6B6B6</Color>
<Color x:Key="AccentColor">#007ACC</Color>
<Color x:Key="BorderColor">#3F3F46</Color>
<!-- Brushes -->
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{StaticResource PrimaryBackgroundColor}"/>
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{StaticResource SecondaryBackgroundColor}"/>
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="{StaticResource TertiaryBackgroundColor}"/>
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="{StaticResource PrimaryForegroundColor}"/>
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="{StaticResource SecondaryForegroundColor}"/>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
<!-- Control Styles -->
<Style TargetType="{x:Type Window}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="CaretBrush" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type ListView}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1,1,1,0"
Margin="2,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="Border" Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource SecondaryForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="RowBackground" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="AlternatingRowBackground" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Menu}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Style.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type StatusBar}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type ToolBar}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
</Style>
<!--<Style x:Key="DarkModeToggleSwitch" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border x:Name="BackgroundBorder"
Background="#333333"
CornerRadius="10"
Width="50"
Height="24"/>
<Border x:Name="SwitchThumb"
Background="White"
CornerRadius="10"
Width="20"
Height="20"
HorizontalAlignment="Left"
Margin="2,0,0,0">
<Border.Effect>
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.3"/>
</Border.Effect>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="#0078D7"/>
<Setter TargetName="SwitchThumb" Property="Margin" Value="28,0,0,0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
</ResourceDictionary>
@@ -0,0 +1,239 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Base Colors -->
<Color x:Key="PrimaryBackgroundColor">#FFFFFF</Color>
<Color x:Key="SecondaryBackgroundColor">#F0F0F0</Color>
<Color x:Key="TertiaryBackgroundColor">#E6E6E6</Color>
<Color x:Key="PrimaryForegroundColor">#000000</Color>
<Color x:Key="SecondaryForegroundColor">#555555</Color>
<Color x:Key="AccentColor">#0078D7</Color>
<Color x:Key="BorderColor">#CCCCCC</Color>
<!-- Brushes -->
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{StaticResource PrimaryBackgroundColor}"/>
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{StaticResource SecondaryBackgroundColor}"/>
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="{StaticResource TertiaryBackgroundColor}"/>
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="{StaticResource PrimaryForegroundColor}"/>
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="{StaticResource SecondaryForegroundColor}"/>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
<!-- Control Styles -->
<Style TargetType="{x:Type Window}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="CaretBrush" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type ListView}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1,1,1,0"
Margin="2,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="Border" Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource SecondaryForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="RowBackground" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="AlternatingRowBackground" Value="{StaticResource SecondaryBackgroundBrush}"/>
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Menu}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
<Style.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type StatusBar}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForegroundBrush}"/>
</Style>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="{StaticResource SecondaryBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type ToolBar}">
<Setter Property="Background" Value="{StaticResource TertiaryBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}"/>
</Style>
<!-- Custom ToggleSwitch Style -->
<Style x:Key="DarkModeToggleSwitch" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border x:Name="BackgroundBorder"
Background="#CCCCCC"
CornerRadius="10"
Width="50"
Height="24"/>
<Border x:Name="SwitchThumb"
Background="White"
CornerRadius="10"
Width="20"
Height="20"
HorizontalAlignment="Left"
Margin="2,0,0,0">
<Border.Effect>
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.3"/>
</Border.Effect>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="#0078D7"/>
<Setter TargetName="SwitchThumb" Property="Margin" Value="28,0,0,0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Themes/DarkTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
+107
View File
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace NESDecompiler.GUI.Themes
{
public class ThemeManager
{
private static ThemeManager _instance;
public static ThemeManager Instance => _instance ??= new ThemeManager();
private readonly Dictionary<string, object> _themeResources = new Dictionary<string, object>();
private ThemeManager()
{
IsDarkTheme = true;
}
public bool IsDarkTheme { get; private set; }
public void ToggleTheme()
{
IsDarkTheme = !IsDarkTheme;
ApplyTheme();
}
public void SetTheme(bool isDarkTheme)
{
if (IsDarkTheme != isDarkTheme)
{
IsDarkTheme = isDarkTheme;
ApplyTheme();
}
}
public void ApplyTheme()
{
try
{
var appResources = Application.Current.Resources.MergedDictionaries;
_themeResources.Clear();
for (int i = appResources.Count - 1; i >= 0; i--)
{
var resourceUri = appResources[i].Source?.ToString();
if (resourceUri != null &&
(resourceUri.Contains("DarkTheme") || resourceUri.Contains("LightTheme")))
{
appResources.RemoveAt(i);
}
}
var themeDictionary = new ResourceDictionary
{
Source = new Uri(
$"pack://application:,,,/Resources/Themes/{(IsDarkTheme ? "Dark" : "Light")}Theme.xaml",
UriKind.Absolute)
};
appResources.Insert(0, themeDictionary);
UpdateResourcesExplicitly(themeDictionary);
ThemeChanged?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
MessageBox.Show($"Error applying theme: {ex.Message}", "Theme Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void UpdateResourcesExplicitly(ResourceDictionary themeDictionary)
{
UpdateResourceIfExists(themeDictionary, "PrimaryBackgroundBrush");
UpdateResourceIfExists(themeDictionary, "SecondaryBackgroundBrush");
UpdateResourceIfExists(themeDictionary, "TertiaryBackgroundBrush");
UpdateResourceIfExists(themeDictionary, "PrimaryForegroundBrush");
UpdateResourceIfExists(themeDictionary, "SecondaryForegroundBrush");
UpdateResourceIfExists(themeDictionary, "AccentBrush");
UpdateResourceIfExists(themeDictionary, "BorderBrush");
}
private void UpdateResourceIfExists(ResourceDictionary dictionary, string key)
{
if (dictionary.Contains(key))
{
_themeResources[key] = dictionary[key];
Application.Current.Resources[key] = dictionary[key];
}
}
public object GetResource(string key)
{
if (_themeResources.TryGetValue(key, out var resource))
{
return resource;
}
return Application.Current.Resources[key];
}
public event EventHandler ThemeChanged;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace NESDecompiler.GUI.ViewModels
{
/// <summary>
/// Types of project items
/// </summary>
public enum ProjectItemType
{
Project,
ROM,
ROMInfo,
Disassembly,
CCode,
Header,
Variables,
Functions
}
/// <summary>
/// View model for a project item in the tree view
/// </summary>
public class ProjectItemViewModel : INotifyPropertyChanged
{
private string name = "";
private ProjectItemType type;
private string path = "";
/// <summary>
/// The name of this item
/// </summary>
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public class RecentProjectInfo
{
public string FilePath { get; set; }
public string Name { get; set; }
public DateTime LastOpened { get; set; }
}
/// <summary>
/// The type of this item
/// </summary>
public ProjectItemType Type
{
get => type;
set
{
type = value;
OnPropertyChanged(nameof(Type));
OnPropertyChanged(nameof(Icon));
}
}
/// <summary>
/// The file path of this item
/// </summary>
public string Path
{
get => path;
set
{
path = value;
OnPropertyChanged(nameof(Path));
}
}
/// <summary>
/// The icon for this item type
/// </summary>
public string Icon
{
get
{
return Type switch
{
ProjectItemType.ROM => "/Resources/Icons/rom.png",
ProjectItemType.ROMInfo => "/Resources/Icons/info.png",
ProjectItemType.Disassembly => "/Resources/Icons/asm.png",
ProjectItemType.CCode => "/Resources/Icons/c.png",
ProjectItemType.Header => "/Resources/Icons/h.png",
ProjectItemType.Variables => "/Resources/Icons/var.png",
ProjectItemType.Functions => "/Resources/Icons/func.png",
_ => "/Resources/Icons/file.png"
};
}
}
/// <summary>
/// Child items for this item
/// </summary>
public ObservableCollection<ProjectItemViewModel> Children { get; } = new ObservableCollection<ProjectItemViewModel>();
private bool isExpanded = false;
public bool IsExpanded
{
get => isExpanded;
set
{
isExpanded = value;
OnPropertyChanged(nameof(IsExpanded));
if (Tag is ROMProject romProject)
{
romProject.IsExpanded = value;
}
}
}
public object Tag { get; set; }
#region INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
@@ -0,0 +1,127 @@
using System.ComponentModel;
public class VariableViewModel : INotifyPropertyChanged
{
private ushort address;
private string name;
private string typeName;
private int size;
private bool isRead;
private bool isWritten;
private string description;
public ushort Address
{
get => address;
set { address = value; OnPropertyChanged(nameof(Address)); OnPropertyChanged(nameof(AddressHex)); }
}
public string AddressHex => $"0x{Address:X4}";
public string Name
{
get => name;
set { name = value; OnPropertyChanged(nameof(Name)); }
}
public string TypeName
{
get => typeName;
set { typeName = value; OnPropertyChanged(nameof(TypeName)); }
}
public int Size
{
get => size;
set { size = value; OnPropertyChanged(nameof(Size)); }
}
public bool IsRead
{
get => isRead;
set { isRead = value; OnPropertyChanged(nameof(IsRead)); OnPropertyChanged(nameof(AccessType)); }
}
public bool IsWritten
{
get => isWritten;
set { isWritten = value; OnPropertyChanged(nameof(IsWritten)); OnPropertyChanged(nameof(AccessType)); }
}
public string AccessType => $"{(IsRead ? "Read" : "")}{(IsRead && IsWritten ? "/" : "")}{(IsWritten ? "Write" : "")}";
public string Description
{
get => description;
set { description = value; OnPropertyChanged(nameof(Description)); }
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FunctionViewModel : INotifyPropertyChanged
{
private ushort address;
private string name;
private int instructionCount;
private int variableCount;
private int calledFunctionCount;
private List<string> parameters = new List<string>();
private string description;
public ushort Address
{
get => address;
set { address = value; OnPropertyChanged(nameof(Address)); OnPropertyChanged(nameof(AddressHex)); }
}
public string AddressHex => $"0x{Address:X4}";
public string Name
{
get => name;
set { name = value; OnPropertyChanged(nameof(Name)); }
}
public int InstructionCount
{
get => instructionCount;
set { instructionCount = value; OnPropertyChanged(nameof(InstructionCount)); }
}
public int VariableCount
{
get => variableCount;
set { variableCount = value; OnPropertyChanged(nameof(VariableCount)); }
}
public int CalledFunctionCount
{
get => calledFunctionCount;
set { calledFunctionCount = value; OnPropertyChanged(nameof(CalledFunctionCount)); }
}
public List<string> Parameters
{
get => parameters;
set { parameters = value; OnPropertyChanged(nameof(Parameters)); }
}
public string Description
{
get => description;
set { description = value; OnPropertyChanged(nameof(Description)); }
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
@@ -0,0 +1,934 @@
using System;
using System.IO;
using System.Text;
using System.Windows;
using Microsoft.Win32;
using System.Diagnostics;
using System.Threading.Tasks;
namespace NESDecompiler.GUI.ViewModels
{
/// <summary>
/// Helper class for exporting to Visual Studio 2022 solution
/// </summary>
public class VisualStudioExporter
{
private readonly string solutionName;
private readonly string rootPath;
private readonly string cCode;
private readonly string headerCode;
/// <summary>
/// Creates a new Visual Studio exporter
/// </summary>
/// <param name="solutionName">The name of the solution</param>
/// <param name="rootPath">The root directory path</param>
/// <param name="cCode">The C code to export</param>
/// <param name="headerCode">The header code to export</param>
public VisualStudioExporter(string solutionName, string rootPath, string cCode, string headerCode)
{
this.solutionName = solutionName;
this.rootPath = rootPath;
this.cCode = cCode;
this.headerCode = headerCode;
}
/// <summary>
/// Exports the decompiled code as a Visual Studio 2022 solution
/// </summary>
/// <returns>True if successful, false otherwise</returns>
public bool ExportToVisualStudio()
{
try
{
string solutionDir = Path.Combine(rootPath, solutionName);
Directory.CreateDirectory(solutionDir);
string sourceDir = Path.Combine(solutionDir, "src");
Directory.CreateDirectory(sourceDir);
File.WriteAllText(Path.Combine(sourceDir, $"{solutionName}.c"), cCode);
File.WriteAllText(Path.Combine(sourceDir, $"{solutionName}.h"), headerCode);
CreateNesPlatformHeader(sourceDir);
CreateMainFile(sourceDir);
CreateCMakeFile(solutionDir);
CreateSolutionFile(solutionDir);
CreateProjectFile(solutionDir);
CreateFiltersFile(solutionDir);
CreateReadmeFile(solutionDir);
return true;
}
catch (Exception ex)
{
MessageBox.Show($"Failed to export to Visual Studio: {ex.Message}", "Export Error", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}
/// <summary>
/// Creates a NES platform header file with platform-specific declarations
/// </summary>
private void CreateNesPlatformHeader(string sourceDir)
{
string content = @"#ifndef NES_PLATFORM_H
#define NES_PLATFORM_H
#include <stdint.h>
#include <stdbool.h>
// NES PPU registers enum (for use in switch statements)
typedef enum {
PPUCTRL_REG = 0x2000,
PPUMASK_REG = 0x2001,
PPUSTATUS_REG = 0x2002,
OAMADDR_REG = 0x2003,
OAMDATA_REG = 0x2004,
PPUSCROLL_REG = 0x2005,
PPUADDR_REG = 0x2006,
PPUDATA_REG = 0x2007,
OAMDMA_REG = 0x4014
} NESRegisters;
// NES PPU registers (Memory mapped hardware registers)
#define PPUCTRL 0x2000
#define PPUMASK 0x2001
#define PPUSTATUS 0x2002
#define OAMADDR 0x2003
#define OAMDATA 0x2004
#define PPUSCROLL 0x2005
#define PPUADDR 0x2006
#define PPUDATA 0x2007
#define OAMDMA 0x4014
// NES APU registers
#define APU_PULSE1_1 0x4000
#define APU_PULSE1_2 0x4001
#define APU_PULSE1_3 0x4002
#define APU_PULSE1_4 0x4003
#define APU_PULSE2_1 0x4004
#define APU_PULSE2_2 0x4005
#define APU_PULSE2_3 0x4006
#define APU_PULSE2_4 0x4007
#define APU_TRI_1 0x4008
#define APU_TRI_2 0x4009
#define APU_TRI_3 0x400A
#define APU_TRI_4 0x400B
#define APU_NOISE_1 0x400C
#define APU_NOISE_2 0x400D
#define APU_NOISE_3 0x400E
#define APU_NOISE_4 0x400F
#define APU_DMC_1 0x4010
#define APU_DMC_2 0x4011
#define APU_DMC_3 0x4012
#define APU_DMC_4 0x4013
#define APU_STATUS 0x4015
#define APU_FRAMECNT 0x4017
// NES Joypad registers
#define JOY1 0x4016
#define JOY2 0x4017
// CPU Status Flag Masks (same as in the decompiled code)
#define CARRY_FLAG 0x01
#define ZERO_FLAG 0x02
#define INTERRUPT_FLAG 0x04
#define DECIMAL_FLAG 0x08
#define BREAK_FLAG 0x10
#define UNUSED_FLAG 0x20
#define OVERFLOW_FLAG 0x40
#define NEGATIVE_FLAG 0x80
// Memory map constants
#define RAM_START 0x0000
#define RAM_SIZE 0x0800
#define RAM_MIRROR_END 0x1FFF
#define PPU_START 0x2000
#define PPU_SIZE 0x0008
#define PPU_MIRROR_END 0x3FFF
#define APU_IO_START 0x4000
#define APU_IO_END 0x4017
#define APU_IO_SIZE (APU_IO_END - APU_IO_START + 1)
#define CART_START 0x4020
#define CART_END 0xFFFF
// Platform abstraction layer
typedef struct {
uint8_t memory[0x10000]; // 64KB of addressable memory
uint8_t ppu_memory[0x4000]; // 16KB of PPU memory
// CPU registers
uint8_t a; // Accumulator
uint8_t x; // X register
uint8_t y; // Y register
uint8_t sp; // Stack pointer
uint8_t status; // Status register
uint16_t pc; // Program counter
// Input state
uint8_t joy1_state;
uint8_t joy2_state;
// Timing
uint64_t cycles;
// Debugger state
bool debug_enabled;
bool breakpoints[0x10000]; // One per memory address
} NESPlatform;
// Initialize the NES platform
void nes_platform_init(NESPlatform* platform);
// Reset the NES platform
void nes_platform_reset(NESPlatform* platform);
// Read a byte from memory
uint8_t nes_platform_read(NESPlatform* platform, uint16_t address);
// Write a byte to memory
void nes_platform_write(NESPlatform* platform, uint16_t address, uint8_t value);
// Push a byte to the stack
void nes_platform_push(NESPlatform* platform, uint8_t value);
// Pop a byte from the stack
uint8_t nes_platform_pop(NESPlatform* platform);
// Execute a single instruction
void nes_platform_execute(NESPlatform* platform);
// Platform abstraction for running decompiled code
void nes_platform_run_decompiled(NESPlatform* platform);
// Debug functions
void nes_platform_dump_memory(NESPlatform* platform, uint16_t start, uint16_t end);
void nes_platform_set_breakpoint(NESPlatform* platform, uint16_t address, bool enabled);
void nes_platform_dump_cpu_state(NESPlatform* platform);
#endif
";
File.WriteAllText(Path.Combine(sourceDir, "nes_platform.h"), content);
}
/// <summary>
/// Creates a main.c file that includes the decompiled code
/// </summary>
private void CreateMainFile(string sourceDir)
{
string content = $@"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include ""nes_platform.h""
#include ""{solutionName}.h""
// Implementation of platform abstraction layer
NESPlatform nes;
// Debug flag
bool debug_enabled = false;
// Read a byte from NES memory with proper memory mapping
uint8_t nes_platform_read(NESPlatform* platform, uint16_t address) {{
// Apply memory mirroring for RAM
if (address < 0x2000) {{
// 2KB RAM with 3 mirrors (0x0000-0x07FF repeated 4 times)
return platform->memory[address & 0x07FF];
}}
// PPU registers with mirrors
else if (address < 0x4000) {{
// 8 bytes mirrored throughout 0x2000-0x3FFF
return platform->memory[0x2000 + (address & 0x7)];
}}
// Direct reads for everything else
else {{
return platform->memory[address];
}}
}}
// Write a byte to NES memory with proper memory mapping
void nes_platform_write(NESPlatform* platform, uint16_t address, uint8_t value) {{
if (platform->debug_enabled) {{
printf(""Write: 0x%04X = 0x%02X\n"", address, value);
}}
// Apply memory mirroring for RAM
if (address < 0x2000) {{
// 2KB RAM with 3 mirrors (0x0000-0x07FF repeated 4 times)
platform->memory[address & 0x07FF] = value;
}}
// PPU registers with mirrors
else if (address < 0x4000) {{
// 8 bytes mirrored throughout 0x2000-0x3FFF
uint16_t register_addr = 0x2000 + (address & 0x7);
platform->memory[register_addr] = value;
// Special handling for certain PPU registers
switch (register_addr) {{
case PPUCTRL_REG:
// Handle PPUCTRL writes
break;
case PPUMASK_REG:
// Handle PPUMASK writes
break;
case PPUSTATUS_REG:
// Handle PPUSTATUS writes
break;
case OAMADDR_REG:
// Handle OAMADDR writes
break;
case OAMDATA_REG:
// Handle OAMDATA writes
break;
case PPUSCROLL_REG:
// Handle PPUSCROLL writes
break;
case PPUADDR_REG:
// Handle PPUADDR writes
break;
case PPUDATA_REG:
// Handle PPUDATA writes
break;
default:
// Other registers (shouldn't happen with mask)
break;
}}
}}
// APU and IO registers
else if (address >= 0x4000 && address <= 0x4017) {{
platform->memory[address] = value;
// Special handling for certain registers
if (address == OAMDMA_REG) {{
// Handle OAMDMA (would copy 256 bytes from CPU memory to OAM)
uint16_t oam_src = value << 8; // High byte of address
for (int i = 0; i < 256; i++) {{
platform->memory[0x2004] = nes_platform_read(platform, oam_src + i);
}}
}}
else if (address == JOY1 || address == JOY2) {{
// Handle controller writes
}}
}}
// Cartridge space
else {{
platform->memory[address] = value;
}}
}}
// Push a byte to the stack
void nes_platform_push(NESPlatform* platform, uint8_t value) {{
platform->memory[0x0100 + platform->sp] = value;
platform->sp--;
}}
// Pop a byte from the stack
uint8_t nes_platform_pop(NESPlatform* platform) {{
platform->sp++;
return platform->memory[0x0100 + platform->sp];
}}
void nes_platform_init(NESPlatform* platform) {{
memset(platform, 0, sizeof(NESPlatform));
// Initialize CPU registers
platform->a = 0;
platform->x = 0;
platform->y = 0;
platform->sp = 0xFD; // Initial stack pointer
platform->status = 0x34; // Initial status (I flag and U flag set)
platform->pc = 0x8000; // Start of PRG ROM
// Initialize memory
memset(platform->memory, 0, sizeof(platform->memory));
memset(platform->ppu_memory, 0, sizeof(platform->ppu_memory));
// Enable debugging if requested
platform->debug_enabled = debug_enabled;
}}
void nes_platform_reset(NESPlatform* platform) {{
if (platform->debug_enabled) {{
printf(""Resetting NES platform...\n"");
}}
// Read reset vector
uint16_t reset_vector = platform->memory[0xFFFC] | (platform->memory[0xFFFD] << 8);
if (platform->debug_enabled) {{
printf(""Reset vector: $%04X\n"", reset_vector);
}}
// Set program counter to reset vector
platform->pc = reset_vector;
// Set stack pointer
platform->sp = 0xFD;
// Set status register (I flag and U flag set)
platform->status = 0x34;
}}
// Debug: Dump memory region
void nes_platform_dump_memory(NESPlatform* platform, uint16_t start, uint16_t end) {{
printf(""Memory dump from $%04X to $%04X:\n"", start, end);
for (uint16_t addr = start; addr <= end; addr++) {{
if ((addr % 16) == 0) {{
printf(""\n$%04X: "", addr);
}}
printf(""%02X "", nes_platform_read(platform, addr));
}}
printf(""\n"");
}}
// Debug: Set breakpoint
void nes_platform_set_breakpoint(NESPlatform* platform, uint16_t address, bool enabled) {{
platform->breakpoints[address] = enabled;
printf(""Breakpoint at $%04X %s\n"", address, enabled ? ""enabled"" : ""disabled"");
}}
// Debug: Dump CPU state
void nes_platform_dump_cpu_state(NESPlatform* platform) {{
printf(""CPU State:\n"");
printf("" PC: $%04X\n"", platform->pc);
printf("" A: $%02X\n"", platform->a);
printf("" X: $%02X\n"", platform->x);
printf("" Y: $%02X\n"", platform->y);
printf("" SP: $%02X\n"", platform->sp);
printf("" Status: $%02X [%c%c%c%c%c%c%c%c]\n"",
platform->status,
(platform->status & NEGATIVE_FLAG) ? 'N' : '.',
(platform->status & OVERFLOW_FLAG) ? 'V' : '.',
(platform->status & UNUSED_FLAG) ? 'U' : '.',
(platform->status & BREAK_FLAG) ? 'B' : '.',
(platform->status & DECIMAL_FLAG) ? 'D' : '.',
(platform->status & INTERRUPT_FLAG) ? 'I' : '.',
(platform->status & ZERO_FLAG) ? 'Z' : '.',
(platform->status & CARRY_FLAG) ? 'C' : '.');
uint8_t opcode = nes_platform_read(platform, platform->pc);
printf("" Next: $%04X: $%02X ...\n"", platform->pc, opcode);
}}
// Run the decompiled code using the platform abstraction
void nes_platform_run_decompiled(NESPlatform* platform) {{
printf(""==========================================================\n"");
printf("" {solutionName} - NES Decompiled Code Runner \n"");
printf(""==========================================================\n\n"");
// Print detected entry points
printf(""Trying to identify entry points...\n"");
// Check if reset vector points to a valid location
uint16_t reset_vector = platform->memory[0xFFFC] | (platform->memory[0xFFFD] << 8);
printf(""Reset vector (from 0xFFFC): 0x%04X\n"", reset_vector);
// Try calling entry point functions if they exist
bool entry_point_found = false;
// Define the most common entry points to try
typedef void (*EntryFunc)();
struct {{
const char* name;
EntryFunc func;
bool exists;
}} entry_points[] = {{
#ifdef main
{{ ""main"", (EntryFunc)main, true }},
#else
{{ ""main"", NULL, false }},
#endif
#ifdef reset_handler
{{ ""reset_handler"", reset_handler, true }},
#else
{{ ""reset_handler"", NULL, false }},
#endif
#ifdef nmi_handler
{{ ""nmi_handler"", nmi_handler, true }},
#else
{{ ""nmi_handler"", NULL, false }},
#endif
// Common entry points based on memory location
#ifdef sub_8000
{{ ""sub_8000"", sub_8000, true }},
#else
{{ ""sub_8000"", NULL, false }},
#endif
#ifdef sub_C000
{{ ""sub_C000"", sub_C000, true }},
#else
{{ ""sub_C000"", NULL, false }},
#endif
// Try to match the reset vector if defined as a function
{{ ""reset_vector_func"", NULL, false }}
}};
const int num_entry_points = sizeof(entry_points) / sizeof(entry_points[0]);
// Try each entry point
for (int i = 0; i < num_entry_points; i++) {{
// Special handling for reset vector
if (strcmp(entry_points[i].name, ""reset_vector_func"") == 0) {{
// Try to find a function that matches the reset vector
char reset_func_name[32];
sprintf(reset_func_name, ""sub_%04X"", reset_vector);
printf("" Checking for function matching reset vector: %s"", reset_func_name);
printf("" (manual call required)\n"");
continue;
}}
// Check if this entry point exists
if (entry_points[i].exists && entry_points[i].func != NULL) {{
printf("" Found entry point: %s - Calling...\n"", entry_points[i].name);
// Call the entry point function
entry_points[i].func();
entry_point_found = true;
printf("" Finished executing %s\n"", entry_points[i].name);
break;
}}
else {{
printf("" Entry point not found: %s\n"", entry_points[i].name);
}}
}}
if (!entry_point_found) {{
printf(""\n==========================================================\n"");
printf(""WARNING: Could not find any valid entry point function!\n"");
printf(""You need to manually call one of the decompiled functions.\n"");
printf(""Common entry points may include:\n"");
printf("" - sub_8000 (if ROM execution starts at 0x8000)\n"");
printf("" - sub_C000 (if ROM execution starts at 0xC000)\n"");
printf("" - A function corresponding to the reset vector address\n"");
printf("" e.g., sub_%04X\n"", reset_vector);
printf(""==========================================================\n"");
}}
printf(""\nDecompiled code execution finished.\n"");
}}
// Main function
int main(int argc, char* argv[]) {{
for (int i = 1; i < argc; i++) {{
if (strcmp(argv[i], ""--debug"") == 0) {{
debug_enabled = true;
printf(""Debug mode enabled\n"");
}}
}}
nes_platform_init(&nes);
nes_platform_run_decompiled(&nes);
// If debugging, dump final state
if (debug_enabled) {{
nes_platform_dump_cpu_state(&nes);
}}
return 0;
}}
";
File.WriteAllText(Path.Combine(sourceDir, "main.c"), content);
}
/// <summary>
/// Creates a CMakeLists.txt file for better project configuration
/// </summary>
private void CreateCMakeFile(string solutionDir)
{
string content = $@"cmake_minimum_required(VERSION 3.10)
project({solutionName} C)
# Set C standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Add source files
file(GLOB SOURCES src/*.c)
file(GLOB HEADERS src/*.h)
# Create executable
add_executable(${{PROJECT_NAME}} ${{SOURCES}} ${{HEADERS}})
# Add include directories
target_include_directories(${{PROJECT_NAME}} PRIVATE src)
# Set warning level
if(MSVC)
target_compile_options(${{PROJECT_NAME}} PRIVATE /W4)
else()
target_compile_options(${{PROJECT_NAME}} PRIVATE -Wall -Wextra -pedantic)
endif()
# Output binary to bin directory
set_target_properties(${{PROJECT_NAME}} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${{CMAKE_BINARY_DIR}}/bin
)
# Install target
install(TARGETS ${{PROJECT_NAME}} DESTINATION bin)
";
File.WriteAllText(Path.Combine(solutionDir, "CMakeLists.txt"), content);
}
/// <summary>
/// Creates the Visual Studio solution file
/// </summary>
private void CreateSolutionFile(string solutionDir)
{
// Generate a unique GUID for the solution
string solutionGuid = Guid.NewGuid().ToString("B").ToUpper();
string projectGuid = Guid.NewGuid().ToString("B").ToUpper();
string content = $@"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project(""{projectGuid}"") = ""{solutionName}"", ""{solutionName}.vcxproj"", ""{Guid.NewGuid():B}""
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{{{Guid.NewGuid()}}}.Debug|x64.ActiveCfg = Debug|x64
{{{Guid.NewGuid()}}}.Debug|x64.Build.0 = Debug|x64
{{{Guid.NewGuid()}}}.Debug|x86.ActiveCfg = Debug|Win32
{{{Guid.NewGuid()}}}.Debug|x86.Build.0 = Debug|Win32
{{{Guid.NewGuid()}}}.Release|x64.ActiveCfg = Release|x64
{{{Guid.NewGuid()}}}.Release|x64.Build.0 = Release|x64
{{{Guid.NewGuid()}}}.Release|x86.ActiveCfg = Release|Win32
{{{Guid.NewGuid()}}}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {solutionGuid}
EndGlobalSection
EndGlobal
";
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.sln"), content);
}
/// <summary>
/// Creates the Visual Studio project file
/// </summary>
private void CreateProjectFile(string solutionDir)
{
string projectGuid = Guid.NewGuid().ToString().ToUpper();
string content = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<ItemGroup Label=""ProjectConfigurations"">
<ProjectConfiguration Include=""Debug|Win32"">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include=""Release|Win32"">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include=""Debug|x64"">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include=""Release|x64"">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label=""Globals"">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{{{projectGuid}}}</ProjectGuid>
<RootNamespace>{solutionName}</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.Default.props"" />
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"" Label=""Configuration"">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"" Label=""Configuration"">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"" Label=""Configuration"">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"" Label=""Configuration"">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.props"" />
<ImportGroup Label=""ExtensionSettings"">
</ImportGroup>
<ImportGroup Label=""Shared"">
</ImportGroup>
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
</ImportGroup>
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
</ImportGroup>
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
</ImportGroup>
<ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
<Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
</ImportGroup>
<PropertyGroup Label=""UserMacros"" />
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|Win32'"">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Release|Win32'"">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Debug|x64'"">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)'=='Release|x64'"">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include=""src\main.c"" />
<ClCompile Include=""src\{solutionName}.c"" />
</ItemGroup>
<ItemGroup>
<ClInclude Include=""src\nes_platform.h"" />
<ClInclude Include=""src\{solutionName}.h"" />
</ItemGroup>
<Import Project=""$(VCTargetsPath)\Microsoft.Cpp.targets"" />
<ImportGroup Label=""ExtensionTargets"">
</ImportGroup>
</Project>
";
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.vcxproj"), content);
}
/// <summary>
/// Creates the Visual Studio project filters file
/// </summary>
private void CreateFiltersFile(string solutionDir)
{
string content = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<ItemGroup>
<Filter Include=""Source Files"">
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include=""Header Files"">
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include=""Resource Files"">
<UniqueIdentifier>{{{Guid.NewGuid()}}}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include=""src\main.c"">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include=""src\{solutionName}.c"">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include=""src\nes_platform.h"">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include=""src\{solutionName}.h"">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
";
File.WriteAllText(Path.Combine(solutionDir, $"{solutionName}.vcxproj.filters"), content);
}
/// <summary>
/// Creates a README file with instructions
/// </summary>
private void CreateReadmeFile(string solutionDir)
{
string content = $@"# {solutionName} - Decompiled NES ROM
This project contains a decompiled version of a NES ROM, converted to C code that can be compiled and run on modern platforms.
## Project Structure
- `src/{solutionName}.c` - The main decompiled code
- `src/{solutionName}.h` - Header file with function and variable declarations
- `src/nes_platform.h` - Platform abstraction layer for NES hardware
- `src/main.c` - Entry point that sets up the environment and runs the decompiled code
## Building the Project
### Using Visual Studio 2022
1. Open the solution file ({solutionName}.sln) in Visual Studio 2022
2. Select the desired build configuration (Debug/Release) and platform (x86/x64)
3. Build the solution (F7 or Build > Build Solution)
4. Run the program (F5 or Debug > Start Debugging)
### Using CMake
```
mkdir build
cd build
cmake ..
cmake --build .
```
## Running the Decompiled Code
The decompiled code is encapsulated in a wrapper that provides the necessary NES hardware emulation layer. This allows the code to run on modern systems without requiring a full NES emulator.
## Adapting for Other Platforms
To port this code to other platforms:
1. Modify the `nes_platform.h` file to match the target platform's capabilities
2. Update memory access patterns in `main.c` if needed
3. Replace platform-specific code with equivalents for the target platform
## Notes on Decompilation
The decompilation process attempts to recreate the original source code based on the binary ROM. Some aspects of the original code may be approximated or reconstructed in a way that produces equivalent behavior but doesn't match the original source exactly.
- Function boundaries may not exactly match the original source
- Variable names are chosen by the decompiler and may not match original names
- Control flow structures may be simplified or restructured
- Code might not Compile directly and needs Modification, in this current state its more Pseudo-C than anything :p
## License
This decompiled code is provided for educational and research purposes only. All intellectual property rights to the original software remain with their respective owners.
";
File.WriteAllText(Path.Combine(solutionDir, "README.md"), content);
}
/// <summary>
/// Opens the solution in Visual Studio if available
/// </summary>
/// <param name="solutionPath">The path to the solution file</param>
public static void OpenInVisualStudio(string solutionPath)
{
try
{
if (File.Exists(solutionPath))
{
Process.Start(new ProcessStartInfo
{
FileName = solutionPath,
UseShellExecute = true
});
}
}
catch (Exception ex)
{
MessageBox.Show($"Failed to open solution in Visual Studio: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35931.197 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.Core", "NESDecompiler.Core\NESDecompiler.Core.csproj", "{BA91C551-5979-4498-A1C4-BEF79910D7F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.CLI", "NESDecompiler.CLI\NESDecompiler.CLI.csproj", "{38B4A069-4124-4206-BB38-6946ED9BF120}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NESDecompiler.GUI", "NESDecompiler.GUI\NESDecompiler.GUI.csproj", "{DE7EAAE5-189D-435E-8834-54FAE4DA8977}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA91C551-5979-4498-A1C4-BEF79910D7F7}.Release|Any CPU.Build.0 = Release|Any CPU
{38B4A069-4124-4206-BB38-6946ED9BF120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38B4A069-4124-4206-BB38-6946ED9BF120}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38B4A069-4124-4206-BB38-6946ED9BF120}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38B4A069-4124-4206-BB38-6946ED9BF120}.Release|Any CPU.Build.0 = Release|Any CPU
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE7EAAE5-189D-435E-8834-54FAE4DA8977}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {51005478-A548-469F-AEA4-CBB7818DFE04}
EndGlobalSection
EndGlobal
+2
View File
@@ -0,0 +1,2 @@
# NESDecompiler
a NES / 6502 dissasembler / decompiler written in C# DotNet8