Skip to content

Commit

Permalink
Add ability to set the ROM Save File Icon
Browse files Browse the repository at this point in the history
The icon must be a 32x32 PNG file to be supported.
  • Loading branch information
Cuyler36 committed Jul 3, 2018
1 parent 410a0bb commit d29b851
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 80 deletions.
8 changes: 7 additions & 1 deletion ACNESCreator.Core/ACNESCreator.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;net46</TargetFrameworks>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Reference Include="GCNToolKit">
<HintPath>GCNToolKit.dll</HintPath>
</Reference>
</ItemGroup>

</Project>
52 changes: 0 additions & 52 deletions ACNESCreator.Core/EndianExtensions.cs

This file was deleted.

40 changes: 22 additions & 18 deletions ACNESCreator.Core/GCI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public enum Permissions
NoMove = 0b1000 // 8
}

class GCI
public class GCI
{
#region DEFAULT ICON IMAGE DATA
public static readonly byte[] DefaultIconData = new byte[]
Expand Down Expand Up @@ -132,7 +132,11 @@ class GCI
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#endregion

Expand Down Expand Up @@ -163,7 +167,7 @@ public byte[] Data

private byte[] _data;

internal class GCIHeader
public sealed class GCIHeader
{
public string GameCode;
public string MakerCode;
Expand Down Expand Up @@ -242,28 +246,28 @@ public byte[] GetData()
}
}

public GCI(byte[] Data)
{
Header = new GCIHeader(Data);
Comment1 = Encoding.GetEncoding("shift-jis").GetString(Data, Header.CommentOffset + GCIHeaderSize, 32);
Comment2 = Encoding.GetEncoding("shift-jis").GetString(Data, Header.CommentOffset + GCIHeaderSize + 0x20, 32);
if ((IconFormats)Header.IconFormat == IconFormats.Shared_CI8)
{
ImageData = Data.Skip(Header.ImageOffset + GCIHeaderSize).Take(0x5C0).ToArray();
}

this.Data = Data.Skip(0x640).ToArray();
}

public GCI()
public GCI(byte[] IconData = null)
{
Header = new GCIHeader();
Comment1 = "Animal Crossing";
Comment2 = "NES Game";
ImageData = DefaultIconData;
ImageData = IconData ?? DefaultIconData;
Data = new byte[0];
}

public GCI(byte[] HeaderData, byte[] CommentData, byte[] BannerData, byte[] IconData)
{
Header = new GCIHeader(HeaderData);
Comment1 = Encoding.GetEncoding("shift-jis").GetString(CommentData, 0, 32);
Comment2 = Encoding.GetEncoding("shift-jis").GetString(CommentData, 0x20, 32);
if ((IconFormats)Header.IconFormat == IconFormats.Shared_CI8)
{
ImageData = IconData.Skip(Header.ImageOffset + GCIHeaderSize).Take(0x600).ToArray();
}

Data = Data.Skip(0x640).ToArray();
}

public byte[] GetData()
{
List<byte> Data = new List<byte>();
Expand Down
Binary file added ACNESCreator.Core/GCNToolKit.dll
Binary file not shown.
12 changes: 8 additions & 4 deletions ACNESCreator.Core/NES.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,13 @@ public ushort GetSize()

public readonly Region GameRegion;
public readonly bool IsDnMe;
public readonly byte[] SaveIconData;

public NES(string ROMName, byte[] ROMData, Region ACRegion, bool Compress, bool IsGameDnMe)
public NES(string ROMName, byte[] ROMData, Region ACRegion, bool Compress, bool IsGameDnMe, byte[] IconData = null)
{
// Set the icon
SaveIconData = IconData ?? GCI.DefaultIconData;

// Is game Doubutsu no Mori e+?
IsDnMe = IsGameDnMe;

Expand Down Expand Up @@ -157,8 +161,8 @@ public NES(string ROMName, byte[] ROMData, Region ACRegion, bool Compress, bool
GameRegion = ACRegion;
}

public NES(string ROMName, byte[] ROMData, bool CanSave, Region ACRegion, bool Compress, bool IsDnMe)
: this(ROMName, ROMData, ACRegion, Compress, IsDnMe)
public NES(string ROMName, byte[] ROMData, bool CanSave, Region ACRegion, bool Compress, bool IsDnMe, byte[] IconData = null)
: this(ROMName, ROMData, ACRegion, Compress, IsDnMe, IconData)
{
if (!CanSave)
{
Expand Down Expand Up @@ -193,7 +197,7 @@ public byte[] GenerateGCIFile()
}
Data.AddRange(ROM);

var BlankGCIFile = new GCI
var BlankGCIFile = new GCI(SaveIconData)
{
Data = Data.ToArray(),
Comment1 = IsDnMe ? "Animal Forest e+" : "Animal Crossing",
Expand Down
3 changes: 3 additions & 0 deletions ACNESCreator.FrontEnd/ACNESCreator.FrontEnd.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
<Reference Include="Costura, Version=3.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.3.1.0\lib\net46\Costura.dll</HintPath>
</Reference>
<Reference Include="GCNToolKit">
<HintPath>..\ACNESCreator.Core\GCNToolKit.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
Expand Down
16 changes: 12 additions & 4 deletions ACNESCreator.FrontEnd/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<Label x:Name="label" Content="Game Name" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="label1" Content="NES ROM Location" HorizontalAlignment="Left" Margin="10,69,0,0" VerticalAlignment="Top"/>
<Label x:Name="label2" Content="Animal Crossing Region" HorizontalAlignment="Left" Margin="10,128,0,0" VerticalAlignment="Top"/>
<Label Content="Has Save File" HorizontalAlignment="Left" Margin="248,39,0,0" VerticalAlignment="Top"/>
<Label Content="Has Save File" HorizontalAlignment="Left" Margin="248,69,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="GameNameTextBox" HorizontalAlignment="Left" Height="23" Margin="10,41,0,0" VerticalContentAlignment="Center" VerticalAlignment="Top" MaxLength="16" Width="189"/>
<TextBox x:Name="LocationTextBox" HorizontalAlignment="Left" Height="23" Margin="10,100,0,0" VerticalContentAlignment="Center" VerticalAlignment="Top" Width="252"/>
<ComboBox x:Name="RegionComboBox" HorizontalAlignment="Left" Margin="10,159,0,0" VerticalAlignment="Top" Width="110" IsReadOnly="True" SelectedIndex="0">
Expand All @@ -20,12 +20,20 @@
<ComboBoxItem Content="Australia"/>
</ComboBox>
<Button x:Name="BrowseButton" Content="Browse" HorizontalAlignment="Left" Margin="267,101,0,0" VerticalAlignment="Top" Width="75" Height="21" Click="BrowseButton_Click"/>
<CheckBox x:Name="CanSaveCheckBox" Content="" HorizontalAlignment="Left" Margin="332,45,0,0" VerticalAlignment="Top" />
<CheckBox x:Name="CanSaveCheckBox" Content="" HorizontalAlignment="Left" Margin="332,76,0,0" VerticalAlignment="Top" />
<Button x:Name="GenerateButton" Content="Generate GCI File" HorizontalAlignment="Left" Margin="10,0,0,20" VerticalAlignment="Bottom" Height="20" Width="180" Click="GenerateButton_Click"/>
<ProgressBar x:Name="ProgressBar" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0, 0, 20, 20" Width="100" Height="20"/>
<Label Content="Compress ROM" HorizontalAlignment="Left" Margin="234,157,0,0" VerticalAlignment="Top"/>
<CheckBox x:Name="CompressCheckBox" Content="" HorizontalAlignment="Left" Margin="332,164,0,0" VerticalAlignment="Top" />
<Label Content="Is Dōbutsu no Mori e+" HorizontalAlignment="Left" Margin="197,10,0,0" VerticalAlignment="Top"/>
<CheckBox x:Name="IsDnMe" Content="" HorizontalAlignment="Left" Margin="332,16,0,0" VerticalAlignment="Top" Unchecked="IsDnMe_Checked" Checked="IsDnMe_Checked" />
<Label Content="Is Dōbutsu no Mori e+" HorizontalAlignment="Left" Margin="202,128,0,0" VerticalAlignment="Top"/>
<CheckBox x:Name="IsDnMe" Content="" HorizontalAlignment="Left" Margin="332,135,0,0" VerticalAlignment="Top" Unchecked="IsDnMe_Checked" Checked="IsDnMe_Checked" />
<Image x:Name="IconImage" HorizontalAlignment="Left" Height="32" Margin="300,32,0,0" VerticalAlignment="Top" Width="32">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Import" Click="Import_Click"/>
</ContextMenu>
</Image.ContextMenu>
</Image>
<Label Content="Icon" HorizontalAlignment="Left" Margin="299,10,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
102 changes: 101 additions & 1 deletion ACNESCreator.FrontEnd/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Windows.Media.Imaging;
using System.Linq;
using System;
using System.Windows.Media;
using System.Collections.Generic;
using ACNESCreator.Core;
using GCNToolKit.Formats.Images;
using GCNToolKit.Formats.Colors;

namespace ACNESCreator.FrontEnd
{
Expand All @@ -17,6 +24,10 @@ public partial class MainWindow : Window
{
Filter = "All Supported Files|*.nes;*yaz0;*.bin|NES ROM Files|*.nes|Yaz0 Compressed Files|*.yaz0|Binary Files|*.bin|All Files|*.*"
};
readonly OpenFileDialog SelectIconImageDialog = new OpenFileDialog
{
Filter = "PNG Files|*.png"
};

private bool _inProgress = false;
private bool InProgress
Expand All @@ -36,9 +47,34 @@ private bool InProgress
}
}

private byte[] IconData;

public MainWindow()
{
InitializeComponent();

IconData = GCI.DefaultIconData;
RefreshIconImage();
}

private void RefreshIconImage()
{
ushort[] Palette = new ushort[256];
for (int i = 0; i < 256; i++)
{
Palette[i] = BitConverter.ToUInt16(IconData, 32 * 32 + i * 2).Reverse();
}

IconImage.Source = GetIconImage(IconData.Take(32 * 32).ToArray(), Palette);
}

private static BitmapSource GetIconImage(byte[] IconData, ushort[] Palette)
{
int[] IconImageData = C8.DecodeC8(IconData, Palette, 32, 32);
byte[] IconImageDataArray = new byte[IconImageData.Length * 4];
Buffer.BlockCopy(IconImageData, 0, IconImageDataArray, 0, IconImageDataArray.Length);

return BitmapSource.Create(32, 32, 96, 96, PixelFormats.Bgra32, null, IconImageDataArray, 4 * 32);
}

private void BrowseButton_Click(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -96,7 +132,7 @@ private async void GenerateButton_Click(object sender, RoutedEventArgs e)
NES NESFile = null;
try
{
await Task.Run(() => { NESFile = new NES(GameName, ROMData, HasSaveFile, ACRegion, Compress, DnMe); });
await Task.Run(() => { NESFile = new NES(GameName, ROMData, HasSaveFile, ACRegion, Compress, DnMe, IconData); });
}
catch
{
Expand Down Expand Up @@ -136,5 +172,69 @@ private void IsDnMe_Checked(object sender, RoutedEventArgs e)
RegionComboBox.SelectedIndex = 0;
GameNameTextBox.MaxLength = RegionComboBox.IsEnabled ? 16 : 10;
}

private void Import_Click(object sender, RoutedEventArgs e)
{
if (SelectIconImageDialog.ShowDialog().Value && File.Exists(SelectIconImageDialog.FileName))
{
try
{
BitmapImage Img = new BitmapImage();
Img.BeginInit();
Img.UriSource = new Uri(SelectIconImageDialog.FileName);
Img.EndInit();

if (Img.PixelWidth == 32 && Img.PixelHeight == 32)
{
//IconImage.Source = Img;

// Get the image data
byte[] PixelData = new byte[4 * 32 * 32];
Img.CopyPixels(PixelData, 4 * 32, 0);

int[] ImageData = new int[32 * 32];
Buffer.BlockCopy(PixelData, 0, ImageData, 0, PixelData.Length);

// Convert it to C8 format
IconData = new byte[0x600];
List<ushort> PaletteList = new List<ushort>();

for (int i = 0, idx = 0; i < 32 * 32; i++, idx += 4)
{
ushort RGB5A3Color = RGB5A3.ToRGB5A3(PixelData[idx + 3], PixelData[idx + 2], PixelData[idx + 1], PixelData[idx]);
if (!PaletteList.Contains(RGB5A3Color))
{
PaletteList.Add(RGB5A3Color);
}
}

ushort[] Palette = PaletteList.ToArray();
if (Palette.Length > 256)
{
Array.Resize(ref Palette, 256);
}

C8.EncodeC8(ImageData, Palette, 32, 32).CopyTo(IconData, 0);

for (int i = 0; i < PaletteList.Count; i++)
{
BitConverter.GetBytes(PaletteList[i].Reverse()).CopyTo(IconData, 0x400 + i * 2);
}

// Refresh the Icon Image
RefreshIconImage();
}
else
{
MessageBox.Show("The icon you selected is not 32x32 pixels! Please resize it to that and try again.", "Icon Import Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
catch
{
MessageBox.Show("The icon you selected could not be imported! Please try again.", "Icon Import Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
}

0 comments on commit d29b851

Please sign in to comment.