[melonDS] Redo porting, waterbox style now (#2945)

Co-authored-by: nattthebear <goyuken@gmail.com>
This commit is contained in:
CasualPokePlayer 2021-10-24 11:40:23 -07:00 committed by GitHub
parent f14b057d94
commit 41128abc37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 16726 additions and 1479 deletions

9
.gitmodules vendored
View File

@ -15,10 +15,10 @@
path = submodules/mgba
url = https://github.com/TASEmulators/mgba.git
branch = bizhawk-0.9
[submodule "melonds"]
path = melonds
[submodule "waterbox/melon/melonDS"]
path = waterbox/melon/melonDS
url = https://github.com/TASEmulators/melonDS.git
branch = SuuperW_DSHawk
branch = melonbiz
[submodule "waterbox/musl"]
path = waterbox/musl
url = https://github.com/nattthebear/musl.git
@ -37,3 +37,6 @@
[submodule "submodules/libdarm"]
path = submodules/libdarm
url = https://github.com/jbremer/darm.git
[submodule "submodules/libfwunpack"]
path = submodules/libfwunpack
url = https://github.com/TASEmulators/fwunpack

View File

@ -1,8 +1,14 @@
-- Gives a cross hair UI for the stylus for DS games
local upColor = 'white'
local downColor = 'green'
local dotColor = 'red'
local upColor = 'lime'
local downColor = 'red'
local dotColor = 'blue'
client.setwindowsize(client.getwindowsize()) -- assert a sane resolution
invert = false
-- FIXME: No idea how to get lua to detect inverted screens, set this to true if inverted screens are active
-- (typing "invert = true" without the quotes in the console works)
function Draw(x, y, maxX, maxY, isDown)
color = upColor
@ -10,11 +16,24 @@ function Draw(x, y, maxX, maxY, isDown)
color = downColor
end
gui.drawLine(0, y - 1, maxX, y - 1, color)
gui.drawLine(0, y, maxX, y, color)
gui.drawLine(0, y + 1, maxX, y + 1, color)
gui.drawLine(x - 1, 0, x - 1, maxY, color)
gui.drawLine(x, 0, x, maxY, color)
gui.drawLine(x + 1, 0, x + 1, maxY, color)
if isDown then
gui.drawPixel(x - 1, y - 1, dotColor)
gui.drawPixel(x, y - 1, dotColor)
gui.drawPixel(x + 1, y - 1, dotColor)
gui.drawPixel(x - 1, y, dotColor)
gui.drawPixel(x, y, dotColor)
gui.drawPixel(x + 1, y, dotColor)
gui.drawPixel(x - 1, y + 1, dotColor)
gui.drawPixel(x, y + 1, dotColor)
gui.drawPixel(x + 1, y + 1, dotColor)
end
end
@ -30,21 +49,33 @@ while true do
btns = movie.getinput(emu.framecount() - 1)
end
local x = btns['TouchX']
local y = btns['TouchY']
local isDown = btns['Touch']
local wsz = client.getwindowsize()
local w = client.screenwidth() / wsz
local xo = 0
local yo = 0
-- A bit of a hack to ensure it is not drawing while mouse moving
-- on the top screen
if y == 0 then
x = 0
if w == 512 then -- horizontal
if invert then
xo = xo - 256
else
error("Non-inverted horizontal screens are unsupported")
end
else -- vertical
if invert then
yo = yo - 192
else
-- don't need to do anything here
end
end
local x = btns['Touch X'] + xo
local y = btns['Touch Y'] + yo
local isDown = btns['Touch']
pts = client.transformPoint(x, y)
local tx = pts["x"];
local ty = pts["y"];
gui.DrawNew("native")
Draw(tx, ty, 10000, 10000, isDown)
Draw(tx / wsz, ty / wsz, 10000, 10000, isDown)
emu.frameadvance()
emu.yield()
end

View File

@ -1117,12 +1117,12 @@
}
},
"NDS Controller": {
"TouchX": {
"Touch X": {
"Value": "WMouse X",
"Mult": 1.0,
"Deadzone": 0.0
},
"TouchY": {
"Touch Y": {
"Value": "WMouse Y",
"Mult": 1.0,
"Deadzone": 0.0

BIN
Assets/dll/libfwunpack.dll Normal file

Binary file not shown.

BIN
Assets/dll/libfwunpack.so Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Assets/dll/melonDS.wbx.gz Normal file

Binary file not shown.

Binary file not shown.

View File

@ -12,6 +12,7 @@
;#include gamedb_msx1-L.txt (legacy?)
;#include gamedb_msx2-L.txt (legacy?)
#include gamedb_n64.txt
#include gamedb_nds.txt
#include gamedb_nes.txt
#include gamedb_Odyssey2.txt
#include gamedb_pce_cd.txt

14627
Assets/gamedb/gamedb_nds.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -218,6 +218,7 @@ namespace BizHawk.DATTool
GB3x,
GB4x,
O2,
MSX
MSX,
NDS
}
}

@ -1 +0,0 @@
Subproject commit cbd9adac31ac12469f43efe9486cd8b4fadb011d

View File

@ -488,7 +488,7 @@ namespace BizHawk.Client.Common
private BaseFilter CreateCoreScreenControl()
{
if (GlobalEmulator is MelonDS nds)
if (GlobalEmulator is NDS nds)
{
//TODO: need to pipe layout settings into here now
var filter = new ScreenControlNDS(nds);

View File

@ -174,7 +174,7 @@ namespace BizHawk.Client.Common.Filters
public IGL GL;
public IGuiRenderer GuiRenderer;
private readonly MelonDS nds;
private readonly NDS nds;
//TODO: actually use this
private bool nop = false;
@ -195,7 +195,7 @@ namespace BizHawk.Client.Common.Filters
return new Vector2(r.X, r.Y);
}
public ScreenControlNDS(MelonDS nds)
public ScreenControlNDS(NDS nds)
{
//not sure if we actually need this nds instance yet
this.nds = nds;
@ -218,27 +218,27 @@ namespace BizHawk.Client.Common.Filters
var settings = nds.GetSettings();
//gap only applies to vertical, I guess
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Vertical)
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Vertical)
{
bot.Translate(0, 192);
bot.Translate(0, settings.ScreenGap);
}
else if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Horizontal)
else if (settings.ScreenLayout == NDS.ScreenLayoutKind.Horizontal)
{
bot.Translate(256, 0);
}
else if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Top)
else if (settings.ScreenLayout == NDS.ScreenLayoutKind.Top)
{
//do nothing here, we'll discard bottom screen
}
//this doesn't make any sense, it's likely to be too much for a monitor to gracefully handle too
if (settings.ScreenLayout != MelonDS.ScreenLayoutKind.Horizontal)
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Horizontal)
{
int rot = 0;
if (settings.ScreenRotation == MelonDS.ScreenRotationKind.Rotate90) rot = 90;
if (settings.ScreenRotation == MelonDS.ScreenRotationKind.Rotate180) rot = 180;
if (settings.ScreenRotation == MelonDS.ScreenRotationKind.Rotate270) rot = 270;
if (settings.ScreenRotation == NDS.ScreenRotationKind.Rotate90) rot = 90;
if (settings.ScreenRotation == NDS.ScreenRotationKind.Rotate180) rot = 180;
if (settings.ScreenRotation == NDS.ScreenRotationKind.Rotate270) rot = 270;
top.RotateZ(rot);
bot.RotateZ(rot);
}
@ -283,14 +283,14 @@ namespace BizHawk.Client.Common.Filters
//the size can now be determined in a kind of fluffily magical way by transforming edges and checking the bounds
float fxmin = 100000, fymin = 100000, fxmax = -100000, fymax = -100000;
if (settings.ScreenLayout != MelonDS.ScreenLayoutKind.Bottom)
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Bottom)
{
fxmin = Math.Min(Math.Min(Math.Min(Math.Min(top_TL.X, top_TR.X), top_BL.X), top_BR.X), fxmin);
fymin = Math.Min(Math.Min(Math.Min(Math.Min(top_TL.Y, top_TR.Y), top_BL.Y), top_BR.Y), fymin);
fxmax = Math.Max(Math.Max(Math.Max(Math.Max(top_TL.X, top_TR.X), top_BL.X), top_BR.X), fxmax);
fymax = Math.Max(Math.Max(Math.Max(Math.Max(top_TL.Y, top_TR.Y), top_BL.Y), top_BR.Y), fymax);
}
if (settings.ScreenLayout != MelonDS.ScreenLayoutKind.Top)
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Top)
{
fxmin = Math.Min(Math.Min(Math.Min(Math.Min(bot_TL.X, bot_TR.X), bot_BL.X), bot_BR.X), fxmin);
fymin = Math.Min(Math.Min(Math.Min(Math.Min(bot_TL.Y, bot_TR.Y), bot_BL.Y), bot_BR.Y), fymin);
@ -339,7 +339,12 @@ namespace BizHawk.Client.Common.Filters
public override Vector2 UntransformPoint(string channel, Vector2 point)
{
point = Transform(matBotInvert, point);
var settings = nds.GetSettings();
bool invert = settings.ScreenInvert
&& settings.ScreenLayout != NDS.ScreenLayoutKind.Top
&& settings.ScreenLayout != NDS.ScreenLayoutKind.Bottom;
point = Transform(invert ? matTopInvert : matBotInvert, point);
//hack to accomodate input tracking system's float-point sense (based on the core's VideoBuffer height)
//actually, this is needed for a reason similar to the "TouchScreenStart" that I removed.
@ -348,8 +353,7 @@ namespace BizHawk.Client.Common.Filters
point.Y *= 2;
//in case we're in this layout, we get confused, so fix it
var settings = nds.GetSettings();
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Top) point = new(0.0f, 0.0f);
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Top) point = new(0.0f, 0.0f);
//TODO: we probably need more subtle logic here.
//some capability to return -1,-1 perhaps in case the cursor is nowhere.
@ -385,15 +389,17 @@ namespace BizHawk.Client.Common.Filters
bool renderTop = false;
bool renderBottom = false;
var settings = nds.GetSettings();
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Bottom) renderBottom = true;
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Top) renderTop = true;
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Vertical) renderTop = renderBottom = true;
if (settings.ScreenLayout == MelonDS.ScreenLayoutKind.Horizontal) renderTop = renderBottom = true;
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Bottom) renderBottom = true;
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Top) renderTop = true;
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Vertical) renderTop = renderBottom = true;
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Horizontal) renderTop = renderBottom = true;
bool invert = settings.ScreenInvert && renderTop && renderBottom;
if (renderTop)
{
GuiRenderer.Modelview.Push();
GuiRenderer.Modelview.PreMultiplyMatrix(matTop);
GuiRenderer.Modelview.PreMultiplyMatrix(invert ? matBot : matTop);
GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.0f, 1.0f, 0.5f);
GuiRenderer.Modelview.Pop();
}
@ -401,7 +407,7 @@ namespace BizHawk.Client.Common.Filters
if (renderBottom)
{
GuiRenderer.Modelview.Push();
GuiRenderer.Modelview.PreMultiplyMatrix(matBot);
GuiRenderer.Modelview.PreMultiplyMatrix(invert ? matTop : matBot);
GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.5f, 1.0f, 1.0f);
GuiRenderer.Modelview.Pop();
}

View File

@ -208,9 +208,11 @@ namespace BizHawk.Client.Common
// compute its hash
// NDS's firmware file contains user settings; these are over-written by sync settings, so we shouldn't allow them to impact the hash
var rff = reader.Read(fr.ID == NDS_FIRMWARE
? new FileInfo(Emulation.Cores.Consoles.Nintendo.NDS.MelonDS.CreateModifiedFirmware(userSpec))
: fi);
// fixme: there's way more stuff in nds firmware which can affect the hash. rtc fuckery, mac address, wifi settings, etc etc. a proper way to clear these out needs to come
/*var rff = reader.Read(fr.ID == NDS_FIRMWARE
? new FileInfo(Emulation.Cores.Consoles.Nintendo.NDS.NDS.CreateModifiedFirmware(userSpec))
: fi);*/
var rff = reader.Read(fi);
ri.Size = fi.Length;
ri.Hash = rff.Hash;

View File

@ -42,6 +42,7 @@ namespace BizHawk.Client.Common
// It probably should be moved to a separate place.
["GB_Clock"] = 2097152.0,
["GBA"] = 262144.0 / 4389.0, // 59.7275005696
["NDS"] = 33513982.0 / 560190.0, // 59.8260982881
["GEN"] = 53693175 / (3420.0 * 262),
["GEN_PAL"] = 53203424 / (3420.0 * 313),

View File

@ -1,146 +1,149 @@
using System;
using System.Linq;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores;
using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS;
namespace BizHawk.Client.Common
{
[ImporterFor("DeSmuME", ".dsm")]
internal class DsmImport : MovieImporter
{
protected override void RunImport()
{
Result.Movie.HeaderEntries[HeaderKeys.Platform] = "NDS";
var syncSettings = new MelonDS.MelonSyncSettings();
using var sr = SourceFile.OpenText();
string line;
while ((line = sr.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line[0] == '|')
{
ImportInputFrame(line);
}
else if (line.StartsWith("rerecordCount"))
{
int.TryParse(ParseHeader(line, "rerecordCount"), out var rerecordCount);
Result.Movie.Rerecords = (ulong)rerecordCount;
}
else if (line.StartsWith("firmNickname"))
{
syncSettings.Nickname = ParseHeader(line, "firmNickname");
}
else if (line.StartsWith("firmFavColour"))
{
syncSettings.FavoriteColor = byte.Parse(ParseHeader(line, "firmFavColour"));
}
else if (line.StartsWith("firmBirthDay"))
{
syncSettings.BirthdayDay = byte.Parse(ParseHeader(line, "firmBirthDay"));
}
else if (line.StartsWith("firmBirthMonth"))
{
syncSettings.BirthdayMonth = byte.Parse(ParseHeader(line, "firmBirthMonth"));
}
else if (line.StartsWith("rtcStartNew"))
{
//TODO: what is this format?? 2010-JAN-01 00:00:00:000
//var time = DateTime.Parse(ParseHeader(line, "rtcStartNew"));
//syncSettings.TimeAtBoot = (uint)new DateTimeOffset(time.ToLocalTime()).ToUnixTimeSeconds();
}
else if (line.StartsWith("comment author"))
{
Result.Movie.HeaderEntries[HeaderKeys.Author] = ParseHeader(line, "comment author");
}
else if (line.StartsWith("comment"))
{
Result.Movie.Comments.Add(ParseHeader(line, "comment"));
}
else if (line.ToLower().StartsWith("guid"))
{
// We no longer care to keep this info
}
else
{
Result.Movie.Comments.Add(line); // Everything not explicitly defined is treated as a comment.
}
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings);
}
Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.MelonDS;
}
private readonly string[] _buttons = { "Left", "Right", "Up", "Down", "A", "B", "X", "Y", "L", "R", "Start", "Select" };
private void ImportInputFrame(string line)
{
var controller = new SimpleController
{
Definition = new ControllerDefinition
{
BoolButtons =
{
"Left", "Right", "Up", "Down",
"A", "B", "X", "Y", "L", "R", "Start", "Select", "LidOpen", "LidClose", "Power", "Touch"
}
}.AddXYPair("Touch{0}", AxisPairOrientation.RightAndUp, 0.RangeTo(255), 128, 0.RangeTo(191), 96) //TODO verify direction against hardware
};
controller["LidOpen"] = false;
controller["LidClose"] = false;
controller["Power"] = false;
string[] sections = line.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
if (sections.Length > 0)
{
ProcessCmd(sections[0], controller);
}
if (sections.Length > 1)
{
var mnemonics = sections[1].Take(_buttons.Length).ToList();
controller["Left"] = mnemonics[1] != '.';
controller["Right"] = mnemonics[0] != '.';
controller["Up"] = mnemonics[3] != '.';
controller["Down"] = mnemonics[2] != '.';
controller["A"] = mnemonics[7] != '.';
controller["B"] = mnemonics[6] != '.';
controller["X"] = mnemonics[9] != '.';
controller["Y"] = mnemonics[8] != '.';
controller["L"] = mnemonics[10] != '.';
controller["R"] = mnemonics[11] != '.';
controller["Start"] = mnemonics[4] != '.';
controller["Select"] = mnemonics[5] != '.';
controller["Touch"] = sections[1].Substring(21, 1) != "0";
var touchX = int.Parse(sections[1].Substring(13, 3));
var touchY = int.Parse(sections[1].Substring(17, 3));
controller.AcceptNewAxes(new[]
{
("TouchX", touchX),
("TouchY", touchY)
});
}
Result.Movie.AppendFrame(controller);
}
private void ProcessCmd(string cmd, SimpleController controller)
{
// TODO
}
}
}
using System;
using System.Linq;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores;
using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS;
namespace BizHawk.Client.Common
{
[ImporterFor("DeSmuME", ".dsm")]
internal class DsmImport : MovieImporter
{
protected override void RunImport()
{
Result.Movie.HeaderEntries[HeaderKeys.Platform] = "NDS";
var syncSettings = new NDS.SyncSettings();
using var sr = SourceFile.OpenText();
string line;
while ((line = sr.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line[0] == '|')
{
ImportInputFrame(line);
}
else if (line.StartsWith("rerecordCount"))
{
int.TryParse(ParseHeader(line, "rerecordCount"), out var rerecordCount);
Result.Movie.Rerecords = (ulong)rerecordCount;
}
else if (line.StartsWith("firmNickname"))
{
syncSettings.FirmwareUsername = ParseHeader(line, "firmNickname");
}
else if (line.StartsWith("firmFavColour"))
{
syncSettings.FirmwareFavouriteColour = (NDS.SyncSettings.Color)byte.Parse(ParseHeader(line, "firmFavColour"));
}
else if (line.StartsWith("firmBirthDay"))
{
syncSettings.FirmwareBirthdayDay = byte.Parse(ParseHeader(line, "firmBirthDay"));
}
else if (line.StartsWith("firmBirthMonth"))
{
syncSettings.FirmwareBirthdayMonth = (NDS.SyncSettings.Month)byte.Parse(ParseHeader(line, "firmBirthMonth"));
}
else if (line.StartsWith("rtcStartNew"))
{
//TODO: what is this format?? 2010-JAN-01 00:00:00:000
//var time = DateTime.Parse(ParseHeader(line, "rtcStartNew"));
//syncSettings.TimeAtBoot = (uint)new DateTimeOffset(time.ToLocalTime()).ToUnixTimeSeconds();
}
else if (line.StartsWith("comment author"))
{
Result.Movie.HeaderEntries[HeaderKeys.Author] = ParseHeader(line, "comment author");
}
else if (line.StartsWith("comment"))
{
Result.Movie.Comments.Add(ParseHeader(line, "comment"));
}
else if (line.ToLower().StartsWith("guid"))
{
// We no longer care to keep this info
}
else
{
Result.Movie.Comments.Add(line); // Everything not explicitly defined is treated as a comment.
}
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings);
}
Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.MelonDS;
}
private readonly string[] _buttons = { "Left", "Right", "Up", "Down", "A", "B", "X", "Y", "L", "R", "Start", "Select" };
private void ImportInputFrame(string line)
{
var controller = new SimpleController
{
Definition = new ControllerDefinition
{
BoolButtons =
{
"Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Y", "X", "L", "R", "LidOpen", "LidClose", "Touch", "Power"
}
}.AddXYPair("Touch {0}", AxisPairOrientation.RightAndUp, 0.RangeTo(255), 128, 0.RangeTo(191), 96) //TODO verify direction against hardware
.AddAxis("Mic Input", 0.RangeTo(2047), 0)
.AddAxis("GBA Light Sensor", 0.RangeTo(10), 0)
};
controller["LidOpen"] = false;
controller["LidClose"] = false;
controller["Power"] = false;
string[] sections = line.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
if (sections.Length > 0)
{
ProcessCmd(sections[0], controller);
}
if (sections.Length > 1)
{
var mnemonics = sections[1].Take(_buttons.Length).ToList();
controller["Left"] = mnemonics[1] != '.';
controller["Right"] = mnemonics[0] != '.';
controller["Up"] = mnemonics[3] != '.';
controller["Down"] = mnemonics[2] != '.';
controller["A"] = mnemonics[7] != '.';
controller["B"] = mnemonics[6] != '.';
controller["X"] = mnemonics[9] != '.';
controller["Y"] = mnemonics[8] != '.';
controller["L"] = mnemonics[10] != '.';
controller["R"] = mnemonics[11] != '.';
controller["Start"] = mnemonics[4] != '.';
controller["Select"] = mnemonics[5] != '.';
controller["Touch"] = sections[1].Substring(21, 1) != "0";
var touchX = int.Parse(sections[1].Substring(13, 3));
var touchY = int.Parse(sections[1].Substring(17, 3));
controller.AcceptNewAxes(new[]
{
("Touch X", touchX),
("Touch Y", touchY),
("Mic Input", 0),
("GBA Light Sensor", 0)
});
}
Result.Movie.AppendFrame(controller);
}
private void ProcessCmd(string cmd, SimpleController controller)
{
// TODO
}
}
}

View File

@ -33,7 +33,7 @@ namespace BizHawk.Client.EmuHawk.CoreExtensions
Snes9x => Properties.Resources.Snes9X,
MAME => Properties.Resources.Mame,
MGBAHawk => Properties.Resources.Mgba,
MelonDS => Properties.Resources.MelonDS,
NDS => Properties.Resources.MelonDS,
_ => null
};
}

View File

@ -246,7 +246,6 @@ namespace BizHawk.Client.EmuHawk
this.GBPrinterViewerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.NDSSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.NdsSettingsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.NdsSyncSettingsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.PSXSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.PSXControllerSettingsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
this.PSXOptionsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
@ -1640,21 +1639,14 @@ namespace BizHawk.Client.EmuHawk
// NDSSubMenu
//
this.NDSSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.NdsSettingsMenuItem,
this.NdsSyncSettingsMenuItem});
this.NdsSettingsMenuItem });
this.NDSSubMenu.Text = "NDS";
this.NDSSubMenu.DropDownOpened += new System.EventHandler(this.NDSSubMenu_DropDownOpened);
//
// NdsSettingsMenuItem
//
this.NdsSettingsMenuItem.Text = "Settings...";
this.NdsSettingsMenuItem.Click += new System.EventHandler(this.NDSSettingsMenuItem_Click);
//
// NdsSyncSettingsMenuItem
//
this.NdsSyncSettingsMenuItem.Text = "Sync Settings...";
this.NdsSyncSettingsMenuItem.Click += new System.EventHandler(this.NDSSyncSettingsMenuItem_Click);
//
// PSXSubMenu
//
this.PSXSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
@ -2796,7 +2788,6 @@ namespace BizHawk.Client.EmuHawk
private BizHawk.WinForms.Controls.ToolStripMenuItemEx cpcd1ToolStripMenuItem;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx AmstradCPCNonSyncSettingsToolStripMenuItem;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx NDSSubMenu;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx NdsSyncSettingsMenuItem;
private BizHawk.WinForms.Controls.ToolStripMenuItemEx NdsSettingsMenuItem;
private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator8;
private System.Windows.Forms.ToolStripMenuItem CaptureLuaMenuItem;

View File

@ -1586,26 +1586,12 @@ namespace BizHawk.Client.EmuHawk
Tools.Load<GBPrinterView>();
}
private void NDSSubMenu_DropDownOpened(object sender, EventArgs e)
{
NdsSyncSettingsMenuItem.Enabled = MovieSession.Movie.NotActive();
}
private void NDSSettingsMenuItem_Click(object sender, EventArgs e)
{
GenericCoreConfig.DoDialog(this, "NDS Settings", false, true);
GenericCoreConfig.DoDialog(this, "NDS Settings", false, false);
FrameBufferResized();
}
private void NDSSyncSettingsMenuItem_Click(object sender, EventArgs e)
{
if (Emulator is MelonDS ds)
{
using var form = new NdsSyncSettings(this, ds.GetSyncSettings().Clone());
form.ShowDialog();
}
}
private void PsxSubMenu_DropDownOpened(object sender, EventArgs e)
{
PSXControllerSettingsMenuItem.Enabled = MovieSession.Movie.NotActive();

View File

@ -789,15 +789,15 @@ namespace BizHawk.Client.EmuHawk
private void IncrementDSScreenRotate()
{
if (Emulator is MelonDS ds)
if (Emulator is NDS ds)
{
var settings = ds.GetSettings();
settings.ScreenRotation = settings.ScreenRotation switch
{
MelonDS.ScreenRotationKind.Rotate0 => settings.ScreenRotation = MelonDS.ScreenRotationKind.Rotate90,
MelonDS.ScreenRotationKind.Rotate90 => settings.ScreenRotation = MelonDS.ScreenRotationKind.Rotate180,
MelonDS.ScreenRotationKind.Rotate180 => settings.ScreenRotation = MelonDS.ScreenRotationKind.Rotate270,
MelonDS.ScreenRotationKind.Rotate270 => settings.ScreenRotation = MelonDS.ScreenRotationKind.Rotate0,
NDS.ScreenRotationKind.Rotate0 => settings.ScreenRotation = NDS.ScreenRotationKind.Rotate90,
NDS.ScreenRotationKind.Rotate90 => settings.ScreenRotation = NDS.ScreenRotationKind.Rotate180,
NDS.ScreenRotationKind.Rotate180 => settings.ScreenRotation = NDS.ScreenRotationKind.Rotate270,
NDS.ScreenRotationKind.Rotate270 => settings.ScreenRotation = NDS.ScreenRotationKind.Rotate0,
_ => settings.ScreenRotation
};
ds.PutSettings(settings);
@ -809,7 +809,7 @@ namespace BizHawk.Client.EmuHawk
private void IncrementDSLayout(int delta)
{
bool decrement = delta == -1;
if (Emulator is MelonDS ds)
if (Emulator is NDS ds)
{
var settings = ds.GetSettings();
var num = (int)settings.ScreenLayout;
@ -822,8 +822,8 @@ namespace BizHawk.Client.EmuHawk
num++;
}
var next = (MelonDS.ScreenLayoutKind)Enum.Parse(typeof(MelonDS.ScreenLayoutKind), num.ToString());
if (typeof(MelonDS.ScreenLayoutKind).IsEnumDefined(next))
var next = (NDS.ScreenLayoutKind)Enum.Parse(typeof(NDS.ScreenLayoutKind), num.ToString());
if (typeof(NDS.ScreenLayoutKind).IsEnumDefined(next))
{
settings.ScreenLayout = next;

View File

@ -1,272 +0,0 @@
namespace BizHawk.Client.EmuHawk
{
partial class NdsSyncSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.chkBootToFirmware = new System.Windows.Forms.CheckBox();
this.btnCancel = new System.Windows.Forms.Button();
this.btnSave = new System.Windows.Forms.Button();
this.ttipFirmware = new System.Windows.Forms.ToolTip(this.components);
this.txtName = new System.Windows.Forms.TextBox();
this.label1 = new BizHawk.WinForms.Controls.LocLabelEx();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.numBirthDay = new System.Windows.Forms.NumericUpDown();
this.numBirthMonth = new System.Windows.Forms.NumericUpDown();
this.label3 = new BizHawk.WinForms.Controls.LocLabelEx();
this.cbxFavColor = new System.Windows.Forms.ComboBox();
this.label2 = new BizHawk.WinForms.Controls.LocLabelEx();
this.btnDefault = new System.Windows.Forms.Button();
this.dtpStartupTime = new System.Windows.Forms.DateTimePicker();
this.label4 = new BizHawk.WinForms.Controls.LocLabelEx();
this.groupBox1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numBirthDay)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numBirthMonth)).BeginInit();
this.SuspendLayout();
//
// chkBootToFirmware
//
this.chkBootToFirmware.AutoSize = true;
this.chkBootToFirmware.Location = new System.Drawing.Point(12, 12);
this.chkBootToFirmware.Name = "chkBootToFirmware";
this.chkBootToFirmware.Size = new System.Drawing.Size(102, 17);
this.chkBootToFirmware.TabIndex = 0;
this.chkBootToFirmware.Text = "Boot to firmware";
this.ttipFirmware.SetToolTip(this.chkBootToFirmware, "This option requires that a firmware file be in use, as well as both bios files. " +
"See Config -> Firmwares.");
this.chkBootToFirmware.UseVisualStyleBackColor = true;
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(163, 179);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(60, 23);
this.btnCancel.TabIndex = 1;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.CancelBtn_Click);
//
// btnSave
//
this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnSave.Location = new System.Drawing.Point(97, 179);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(60, 23);
this.btnSave.TabIndex = 1;
this.btnSave.Text = "Save";
this.btnSave.UseVisualStyleBackColor = true;
this.btnSave.Click += new System.EventHandler(this.SaveBtn_Click);
//
// txtName
//
this.txtName.Location = new System.Drawing.Point(50, 16);
this.txtName.MaxLength = 10;
this.txtName.Name = "txtName";
this.txtName.Size = new System.Drawing.Size(94, 20);
this.txtName.TabIndex = 2;
//
// label1
//
this.label1.Location = new System.Drawing.Point(6, 19);
this.label1.Name = "label1";
this.label1.Text = "Name:";
//
// groupBox1
//
this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox1.Controls.Add(this.numBirthDay);
this.groupBox1.Controls.Add(this.numBirthMonth);
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.cbxFavColor);
this.groupBox1.Controls.Add(this.label2);
this.groupBox1.Controls.Add(this.txtName);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Location = new System.Drawing.Point(12, 35);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(211, 110);
this.groupBox1.TabIndex = 4;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Firmware settings";
//
// numBirthDay
//
this.numBirthDay.Location = new System.Drawing.Point(133, 69);
this.numBirthDay.Maximum = new decimal(new int[] {
31,
0,
0,
0});
this.numBirthDay.Name = "numBirthDay";
this.numBirthDay.Size = new System.Drawing.Size(36, 20);
this.numBirthDay.TabIndex = 7;
this.numBirthDay.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// numBirthMonth
//
this.numBirthMonth.Location = new System.Drawing.Point(91, 69);
this.numBirthMonth.Maximum = new decimal(new int[] {
12,
0,
0,
0});
this.numBirthMonth.Name = "numBirthMonth";
this.numBirthMonth.Size = new System.Drawing.Size(36, 20);
this.numBirthMonth.TabIndex = 6;
this.numBirthMonth.Value = new decimal(new int[] {
1,
0,
0,
0});
this.numBirthMonth.ValueChanged += new System.EventHandler(this.numBirthMonth_ValueChanged);
//
// label3
//
this.label3.Location = new System.Drawing.Point(6, 71);
this.label3.Name = "label3";
this.label3.Text = "Birthday (M/D):";
//
// cbxFavColor
//
this.cbxFavColor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbxFavColor.FormattingEnabled = true;
this.cbxFavColor.Items.AddRange(new object[] {
"Gray",
"Brown",
"Red",
"Pink",
"Orange",
"Yellow",
"Lime Green",
"Green",
"Dark Green",
"Sea Green",
"Turquoise",
"Blue",
"Dark Blue",
"Dark Purple",
"Violet",
"Magenta"});
this.cbxFavColor.Location = new System.Drawing.Point(50, 42);
this.cbxFavColor.Name = "cbxFavColor";
this.cbxFavColor.Size = new System.Drawing.Size(94, 21);
this.cbxFavColor.TabIndex = 5;
//
// label2
//
this.label2.Location = new System.Drawing.Point(6, 45);
this.label2.Name = "label2";
this.label2.Text = "Color:";
//
// btnDefault
//
this.btnDefault.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnDefault.Location = new System.Drawing.Point(12, 179);
this.btnDefault.Name = "btnDefault";
this.btnDefault.Size = new System.Drawing.Size(60, 23);
this.btnDefault.TabIndex = 1;
this.btnDefault.Text = "Default";
this.btnDefault.UseVisualStyleBackColor = true;
this.btnDefault.Click += new System.EventHandler(this.DefaultBtn_Click);
//
// dtpStartupTime
//
this.dtpStartupTime.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dtpStartupTime.CustomFormat = "yyyy-MM-dd HH:mm:ss";
this.dtpStartupTime.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
this.dtpStartupTime.Location = new System.Drawing.Point(75, 151);
this.dtpStartupTime.MaxDate = new System.DateTime(2099, 12, 31, 23, 59, 59, 0);
this.dtpStartupTime.MinDate = new System.DateTime(2000, 1, 1, 0, 0, 0, 0);
this.dtpStartupTime.Name = "dtpStartupTime";
this.dtpStartupTime.Size = new System.Drawing.Size(148, 20);
this.dtpStartupTime.TabIndex = 5;
this.dtpStartupTime.Value = new System.DateTime(2000, 1, 1, 0, 0, 0, 0);
//
// label4
//
this.label4.Location = new System.Drawing.Point(12, 154);
this.label4.Name = "label4";
this.label4.Text = "Date/time:";
//
// NdsSettings
//
this.AcceptButton = this.btnDefault;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(243, 222);
this.Controls.Add(this.label4);
this.Controls.Add(this.dtpStartupTime);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.btnDefault);
this.Controls.Add(this.btnSave);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.chkBootToFirmware);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(251, 249);
this.Name = "NdsSettings";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "NDS Sync Settings";
this.Load += new System.EventHandler(this.NDSSettings_Load);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numBirthDay)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numBirthMonth)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox chkBootToFirmware;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnSave;
private System.Windows.Forms.ToolTip ttipFirmware;
private System.Windows.Forms.TextBox txtName;
private BizHawk.WinForms.Controls.LocLabelEx label1;
private System.Windows.Forms.GroupBox groupBox1;
private BizHawk.WinForms.Controls.LocLabelEx label3;
private System.Windows.Forms.ComboBox cbxFavColor;
private BizHawk.WinForms.Controls.LocLabelEx label2;
private System.Windows.Forms.NumericUpDown numBirthDay;
private System.Windows.Forms.NumericUpDown numBirthMonth;
private System.Windows.Forms.Button btnDefault;
private System.Windows.Forms.DateTimePicker dtpStartupTime;
private BizHawk.WinForms.Controls.LocLabelEx label4;
}
}

View File

@ -1,93 +0,0 @@
using System;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS;
namespace BizHawk.Client.EmuHawk
{
public partial class NdsSyncSettings : Form
{
private readonly IMainFormForConfig _mainForm;
private readonly MelonDS.MelonSyncSettings _syncSettings;
public NdsSyncSettings(
IMainFormForConfig mainForm,
MelonDS.MelonSyncSettings syncSettings)
{
_mainForm = mainForm;
_syncSettings = syncSettings;
InitializeComponent();
}
private void NDSSettings_Load(object sender, EventArgs e)
{
chkBootToFirmware.Checked = _syncSettings.BootToFirmware;
txtName.Text = _syncSettings.Nickname;
cbxFavColor.SelectedIndex = _syncSettings.FavoriteColor;
numBirthDay.Value = _syncSettings.BirthdayDay;
numBirthMonth.Value = _syncSettings.BirthdayMonth;
dtpStartupTime.Value = DateTimeOffset.FromUnixTimeSeconds(_syncSettings.TimeAtBoot).UtcDateTime;
}
private void numBirthMonth_ValueChanged(object sender, EventArgs e)
{
switch (numBirthMonth.Value)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
numBirthDay.Maximum = 31;
break;
case 4:
case 6:
case 9:
case 11:
numBirthDay.Maximum = 30;
break;
case 2:
numBirthDay.Maximum = 29;
break;
}
}
private void CancelBtn_Click(object sender, EventArgs e)
{
_mainForm.AddOnScreenMessage("Core emulator settings aborted");
DialogResult = DialogResult.Cancel;
Close();
}
private void SaveBtn_Click(object sender, EventArgs e)
{
_syncSettings.BootToFirmware = chkBootToFirmware.Checked;
_syncSettings.Nickname = txtName.Text;
_syncSettings.FavoriteColor = (byte)cbxFavColor.SelectedIndex;
_syncSettings.BirthdayDay = (byte)numBirthDay.Value;
_syncSettings.BirthdayMonth = (byte)numBirthMonth.Value;
// Converting to local time is necessary, because user-set values are "unspecified" which ToUnixTimeSeconds assumes are local.
// But ToLocalTime assumes these are UTC. So here we are adding and then subtracting the UTC-to-local offset.
_syncSettings.TimeAtBoot = (uint)new DateTimeOffset(dtpStartupTime.Value.ToLocalTime()).ToUnixTimeSeconds();
_mainForm.PutCoreSyncSettings(_syncSettings);
DialogResult = DialogResult.OK;
Close();
}
private void DefaultBtn_Click(object sender, EventArgs e)
{
if (_mainForm.DialogController.ShowMessageBox2("Revert to and save default settings?", "default settings", useOKCancel: true))
{
_mainForm.PutCoreSyncSettings(new MelonDS.MelonSyncSettings());
DialogResult = DialogResult.OK;
Close();
}
}
}
}

View File

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ttipFirmware.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="ttipFirmware.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -144,6 +144,7 @@
"GB3x",
"GB4x",
"GEN",
"NDS",
"PCFX",
"PSX",
"SAT",
@ -227,4 +228,4 @@
private System.Windows.Forms.Button btnRemove;
private System.Windows.Forms.Button SaveButton;
}
}
}

View File

@ -114,7 +114,20 @@ namespace BizHawk.Emulation.Common
FirmwareAndOption("24F67BDEA115A2C847C8813A262502EE1607B7DF", 16384, "NDS", "bios7", "NDS_Bios7.bin", "ARM7 BIOS");
FirmwareAndOption("BFAAC75F101C135E32E2AAF541DE6B1BE4C8C62D", 4096, "NDS", "bios9", "NDS_Bios9.bin", "ARM9 BIOS");
FirmwareAndOption("22A7547DBC302BCBFB4005CFB5A2D426D3F85AC6", 262144, "NDS", "firmware", "NDS_Firmware.bin", "NDS Firmware (note: given hash is with blank user data)");
Firmware("NDS", "firmware", "NDS Firmware");
// throwing a ton of hashes from the interwebs
var knownhack1 = File("22A7547DBC302BCBFB4005CFB5A2D426D3F85AC6", 262144, "NDS_Firmware [b1].bin", "NDS Firmware", "known hack", true);
var knownhack2 = File("AE22DE59FBF3F35CCFBEACAEBA6FA87AC5E7B14B", 262144, "NDS_Firmware [b2].bin", "NDS Firmware", "known hack", true);
var knownhack3 = File("1CF9E67C2C703BB9961BBCDD39CD2C7E319A803B", 262144, "NDS_Firmware [b3].bin", "NDS Firmware", "known hack", true);
var likelygood1 = File("EDE9ADD041614EAA232059C63D8613B83FE4E954", 262144, "NDS_Firmware.bin", "NDS Firmware", "likely good");
var likelygood2 = File("2EF20B45D12CF00657D4B1BD37A5CC8506923440", 262144, "NDS_Firmware.bin", "NDS Firmware", "likely good");
var likelygood3 = File("87DAE2500E889737AF51F4A5B5845770A62482F5", 262144, "NDS_Lite_Firmware.bin", "NDS-Lite Firmware", "likely good");
Option("NDS", "firmware", in knownhack1);
Option("NDS", "firmware", in knownhack2);
Option("NDS", "firmware", in knownhack3);
Option("NDS", "firmware", in likelygood1);
Option("NDS", "firmware", in likelygood2);
Option("NDS", "firmware", in likelygood3);
FirmwareAndOption("E4ED47FAE31693E016B081C6BDA48DA5B70D7CCB", 512, "Lynx", "Boot", "LYNX_boot.img", "Boot Rom");

View File

@ -0,0 +1,104 @@
using BizHawk.BizInvoke;
using BizHawk.Emulation.Cores.Waterbox;
using System;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
public abstract class LibMelonDS : LibWaterboxCore
{
[Flags]
public enum Buttons : uint
{
A = 0x0001,
B = 0x0002,
SELECT = 0x0004,
START = 0x0008,
RIGHT = 0x0010,
LEFT = 0x0020,
UP = 0x0040,
DOWN = 0x0080,
R = 0x0100,
L = 0x0200,
X = 0x0400,
Y = 0x0800,
TOUCH = 0x1000,
LIDOPEN = 0x2000,
LIDCLOSE = 0x4000,
POWER = 0x8000,
}
[Flags]
public enum LoadFlags : uint
{
NONE = 0x00,
USE_REAL_BIOS = 0x01,
SKIP_FIRMWARE = 0x02,
GBA_CART_PRESENT = 0x04,
ACCURATE_AUDIO_BITRATE = 0x08,
FIRMWARE_OVERRIDE = 0x10,
}
[StructLayout(LayoutKind.Sequential)]
public new class FrameInfo : LibWaterboxCore.FrameInfo
{
public long Time;
public Buttons Keys;
public byte TouchX;
public byte TouchY;
public short MicInput;
public byte GBALightSensor;
}
[StructLayout(LayoutKind.Sequential)]
public class FirmwareSettings
{
public IntPtr FirmwareUsername; // max 10 length (then terminator)
public int FirmwareUsernameLength;
public NDS.SyncSettings.Language FirmwareLanguage;
public NDS.SyncSettings.Month FirmwareBirthdayMonth;
public int FirmwareBirthdayDay;
public NDS.SyncSettings.Color FirmwareFavouriteColour;
public IntPtr FirmwareMessage; // max 26 length (then terminator)
public int FirmwareMessageLength;
};
[BizImport(CC)]
public abstract bool Init(LoadFlags flags, FirmwareSettings fwSettings);
[BizImport(CC)]
public abstract void PutSaveRam(byte[] data, uint len);
[BizImport(CC)]
public abstract void GetSaveRam(byte[] data);
[BizImport(CC)]
public abstract int GetSaveRamLength();
[BizImport(CC)]
public abstract bool SaveRamIsDirty();
[BizImport(CC)]
public abstract void GetRegs(uint[] regs);
[BizImport(CC)]
public abstract void SetReg(int ncpu, int index, int val);
// bit 0 -> ARM9 or ARM7
// bit 1 -> ARM or THUMB mode
public enum CpuTypes : uint
{
ARM9,
ARM7,
ARM9_THUMB,
ARM7_THUMB,
}
[UnmanagedFunctionPointer(CC)]
public delegate void TraceCallback(CpuTypes _cpu, IntPtr _regs, uint _opcode, long _ccoffset);
[BizImport(CC)]
public abstract void SetTraceCallback(TraceCallback callback);
}
}

View File

@ -0,0 +1,56 @@
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
partial class NDS : IDebuggable
{
[FeatureNotImplemented]
public IMemoryCallbackSystem MemoryCallbacks => throw new NotImplementedException(); // https://github.com/TASEmulators/BizHawk/issues/2585
public long TotalExecutedCycles => CycleCount;
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
{
uint[] regs = new uint[2 * 16];
_core.GetRegs(regs);
var ret = new Dictionary<string, RegisterValue>();
for (int i = 0; i < 2; i++)
{
int ncpu = i == 0 ? 9 : 7;
for (int j = 0; j < 16; j++)
{
ret["ARM" + ncpu + " r" + j] = regs[i * 16 + j];
}
}
return ret;
}
public void SetCpuRegister(string register, int value)
{
if (register.Length != 7 && register.Length != 8)
{
throw new InvalidOperationException("Wrong String Length???");
}
int ncpu = int.Parse(register.Substring(3, 1));
if (ncpu != 9 && ncpu != 7)
{
throw new InvalidOperationException("Invalid CPU???");
}
int index = int.Parse(register.Substring(6, register.Length - 6));
if (index < 0 || index > 15)
{
throw new InvalidOperationException("Invalid Reg Index???");
}
_core.SetReg(ncpu == 9 ? 0 : 1, index, value);
}
public bool CanStep(StepType type) => false;
[FeatureNotImplemented]
public void Step(StepType type) => throw new NotImplementedException();
}
}

View File

@ -0,0 +1,31 @@
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
partial class NDS : ISaveRam
{
public new bool SaveRamModified => _core.SaveRamIsDirty();
public new byte[] CloneSaveRam()
{
int length = _core.GetSaveRamLength();
if (length > 0)
{
byte[] ret = new byte[length];
_core.GetSaveRam(ret);
return ret;
}
return new byte[0];
}
public new void StoreSaveRam(byte[] data)
{
if (data.Length > 0)
{
_core.PutSaveRam(data, (uint)data.Length);
}
}
}
}

View File

@ -0,0 +1,321 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
using Newtonsoft.Json;
using System;
using System.ComponentModel;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
partial class NDS : ISettable<NDS.Settings, NDS.SyncSettings>
{
private Settings _settings;
private SyncSettings _syncSettings;
public enum ScreenLayoutKind
{
Vertical,
Horizontal,
Top,
Bottom,
}
public enum ScreenRotationKind
{
Rotate0,
Rotate90,
Rotate180,
Rotate270
}
public class Settings
{
[DisplayName("Screen Layout")]
[Description("Adjusts the layout of the screens")]
[DefaultValue(ScreenLayoutKind.Vertical)]
public ScreenLayoutKind ScreenLayout { get; set; }
[DisplayName("Invert Screens")]
[Description("Inverts the order of the screens.")]
[DefaultValue(false)]
public bool ScreenInvert { get; set; }
[DisplayName("Rotation")]
[Description("Adjusts the orientation of the screens")]
[DefaultValue(ScreenRotationKind.Rotate0)]
public ScreenRotationKind ScreenRotation { get; set; }
[JsonIgnore]
private int _screengap;
[DisplayName("Screen Gap")]
[Description("Gap between the screens")]
[DefaultValue(0)]
public int ScreenGap
{
get => _screengap;
set => _screengap = Math.Max(0, Math.Min(128, value));
}
[DisplayName("Accurate Audio Bitrate")]
[Description("If true, the audio bitrate will be set to 10. Otherwise, it will be set to 16.")]
[DefaultValue(true)]
public bool AccurateAudioBitrate { get; set; }
public Settings Clone()
{
return (Settings)MemberwiseClone();
}
public static bool NeedsReboot(Settings x, Settings y)
{
return false;
}
public Settings()
{
SettingsUtil.SetDefaultValues(this);
}
}
private static readonly DateTime minDate = new DateTime(2000, 1, 1);
private static readonly DateTime maxDate = new DateTime(2099, 12, 31, 23, 59, 59);
public class SyncSettings
{
[JsonIgnore]
private DateTime _initaltime;
[DisplayName("Initial Time")]
[Description("Initial time of emulation.")]
[DefaultValue(typeof(DateTime), "2000-01-01")]
public DateTime InitialTime
{
get => _initaltime;
set
{
if (DateTime.Compare(minDate, value) > 0)
{
_initaltime = minDate;
}
else if (DateTime.Compare(maxDate, value) < 0)
{
_initaltime = maxDate;
}
else
{
_initaltime = value;
}
}
}
[DisplayName("Use Real Time")]
[Description("If true, RTC clock will be based off of real time instead of emulated time. Ignored (set to false) when recording a movie.")]
[DefaultValue(false)]
public bool UseRealTime { get; set; }
[DisplayName("Use Real BIOS")]
[Description("If true, real BIOS files will be used.")]
[DefaultValue(false)]
public bool UseRealBIOS { get; set; }
[DisplayName("Skip Firmware")]
[Description("If true, initial firmware boot will be skipped. Forced true if firmware cannot be booted (no real bios or missing firmware).")]
[DefaultValue(false)]
public bool SkipFirmware { get; set; }
[DisplayName("Firmware Override")]
[Description("If true, the firmware settings will be overriden by provided settings. Forced true when recording a movie.")]
[DefaultValue(false)]
public bool FirmwareOverride { get; set; }
public enum StartUp : int
{
AutoBoot,
ManualBoot,
}
[DisplayName("Firmware Start-Up")]
[Description("The way firmware is booted. Auto Boot will go to the game immediately, while Manual Boot will go into the firmware menu. Only applicable if firmware override is in effect.")]
[DefaultValue(StartUp.AutoBoot)]
public StartUp FirmwareStartUp { get; set; }
[JsonIgnore]
private string _firmwareusername;
[DisplayName("Firmware Username")]
[Description("Username in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue("MelonDS")]
public string FirmwareUsername
{
get => _firmwareusername;
set => _firmwareusername = value.Substring(0, Math.Min(10, value.Length));
}
public enum Language : int
{
Japanese,
English,
French,
German,
Italian,
Spanish,
}
[DisplayName("Firmware Language")]
[Description("Language in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue(Language.English)]
public Language FirmwareLanguage { get; set; }
public enum Month : int
{
January = 1,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
}
[JsonIgnore]
private Month _firmwarebirthdaymonth;
[JsonIgnore]
private int _firmwarebirthdayday;
[DisplayName("Firmware Birthday Month")]
[Description("Birthday month in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue(Month.May)]
public Month FirmwareBirthdayMonth
{
get => _firmwarebirthdaymonth;
set
{
FirmwareBirthdayDay = SanitizeBirthdayDay(FirmwareBirthdayDay, value);
_firmwarebirthdaymonth = value;
}
}
[DisplayName("Firmware Birthday Day")]
[Description("Birthday day in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue(15)]
public int FirmwareBirthdayDay
{
get => _firmwarebirthdayday;
set => _firmwarebirthdayday = SanitizeBirthdayDay(value, FirmwareBirthdayMonth);
}
public enum Color : int
{
GreyishBlue,
Brown,
Red,
LightPink,
Orange,
Yellow,
Lime,
LightGreen,
DarkGreen,
Turqoise,
LightBlue,
Blue,
DarkBlue,
DarkPurple,
LightPurple,
DarkPink,
}
[DisplayName("Firmware Favorite Color")]
[Description("Favorite color in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue(Color.Red)]
public Color FirmwareFavouriteColour { get; set; }
[JsonIgnore]
private string _firmwaremessage;
[DisplayName("Firmware Message")]
[Description("Message in firmware. Only applicable if firmware override is in effect.")]
[DefaultValue("Melons Taste Great!")]
public string FirmwareMessage
{
get => _firmwaremessage;
set => _firmwaremessage = value.Substring(0, Math.Min(26, value.Length));
}
public SyncSettings Clone()
{
return (SyncSettings)MemberwiseClone();
}
public static bool NeedsReboot(SyncSettings x, SyncSettings y)
{
return !DeepEquality.DeepEquals(x, y);
}
public SyncSettings()
{
SettingsUtil.SetDefaultValues(this);
}
}
public Settings GetSettings()
{
return _settings.Clone();
}
public SyncSettings GetSyncSettings()
{
return _syncSettings.Clone();
}
public PutSettingsDirtyBits PutSettings(Settings o)
{
var ret = Settings.NeedsReboot(_settings, o);
_settings = o;
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
}
public PutSettingsDirtyBits PutSyncSettings(SyncSettings o)
{
var ret = SyncSettings.NeedsReboot(_syncSettings, o);
_syncSettings = o;
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
}
private static int SanitizeBirthdayDay(int day, SyncSettings.Month fwMonth)
{
int maxdays;
switch (fwMonth)
{
case SyncSettings.Month.February:
{
maxdays = 29;
break;
}
case SyncSettings.Month.April:
case SyncSettings.Month.June:
case SyncSettings.Month.September:
case SyncSettings.Month.November:
{
maxdays = 30;
break;
}
default:
{
maxdays = 31;
break;
}
}
return Math.Max(1, Math.Min(day, maxdays));
}
}
}

View File

@ -0,0 +1,54 @@
using BizHawk.Emulation.Common;
using System;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
partial class NDS
{
private ITraceable Tracer { get; }
private readonly LibMelonDS.TraceCallback _tracecb;
private void MakeTrace(LibMelonDS.CpuTypes _cpu, IntPtr _regs, uint _opcode, long _ccoffset)
{
string cpu = _cpu switch
{
LibMelonDS.CpuTypes.ARM9 => "ARM9",
LibMelonDS.CpuTypes.ARM7 => "ARM7",
LibMelonDS.CpuTypes.ARM9_THUMB => "ARM9 (Thumb)",
LibMelonDS.CpuTypes.ARM7_THUMB => "ARM7 (Thumb)",
_ => throw new InvalidOperationException("Invalid CPU Mode???"),
};
int[] regs = new int[16];
Marshal.Copy(_regs, regs, 0, 16);
bool isthumb = ((uint)_cpu & 2u) == 2u;
uint pc = (uint)regs[15] - (isthumb ? 2u : 4u); // handle prefetch
Tracer.Put(new(
disassembly: string.Format("{0:x8}", pc).PadRight(12) + _disassembler.Trace(pc, _opcode, isthumb).PadRight(32),
registerInfo: string.Format(
"r0:{0:x8} r1:{1:x8} r2:{2:x8} r3:{3:x8} r4:{4:x8} r5:{5:x8} r6:{6:x8} r7:{7:x8} r8:{8:x8} r9:{9:x8} r10:{10:x8} r11:{11:x8} r12:{12:x8} r13:{13:x8} r14:{14:x8} r15:{15:x8} Cy:{16} {17}",
(uint)regs[0],
(uint)regs[1],
(uint)regs[2],
(uint)regs[3],
(uint)regs[4],
(uint)regs[5],
(uint)regs[6],
(uint)regs[7],
(uint)regs[8],
(uint)regs[9],
(uint)regs[10],
(uint)regs[11],
(uint)regs[12],
(uint)regs[13],
(uint)regs[14],
(uint)regs[15],
CycleCount + _ccoffset,
cpu)));
}
}
}

View File

@ -1,187 +1,289 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
[PortedCore(CoreNames.MelonDS, "Arisotura", "0.8.2", "http://melonds.kuribo64.net/", singleInstance: true, isReleased: false)]
public unsafe partial class MelonDS : IEmulator
{
private readonly BasicServiceProvider _serviceProvider;
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
public ControllerDefinition ControllerDefinition { get; }
public int Frame => GetFrameCount();
public string SystemId => "NDS";
public bool DeterministicEmulation => true;
internal CoreComm CoreComm { get; }
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
Deinit();
_resampler.Dispose();
_disposed = true;
}
}
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
{
int buttons = (controller.IsPressed("A") ? 1 : 0) | (controller.IsPressed("B") ? 2 : 0)
| (controller.IsPressed("Select") ? 4 : 0) | (controller.IsPressed("Start") ? 8 : 0)
| (controller.IsPressed("Right") ? 0x10 : 0) | (controller.IsPressed("Left") ? 0x20 : 0)
| (controller.IsPressed("Up") ? 0x40 : 0) | (controller.IsPressed("Down") ? 0x80 : 0)
| (controller.IsPressed("R") ? 0x100 : 0) | (controller.IsPressed("L") ? 0x200 : 0)
| (controller.IsPressed("X") ? 0x400 : 0) | (controller.IsPressed("Y") ? 0x800 : 0)
| (controller.IsPressed("Touch") ? 0x2000 : 0)
| (controller.IsPressed("LidOpen") ? 0x4000 : 0) | (controller.IsPressed("LidClose") ? 0x8000 : 0)
| (controller.IsPressed("Power") ? 0x10000 : 0);
FrameAdvance((uint)buttons, (byte)controller.AxisValue("TouchX"), (byte)controller.AxisValue("TouchY"));
_getNewBuffer = true;
return true;
}
public void ResetCounters()
{
_ResetCounters();
}
// debug path/build for easier testing
//const string dllPath = "../../MelonDS/build/libmelonDS.dll";
private const string dllPath = "dll/libmelonDS.dll";
[DllImport(dllPath)]
private static extern bool Init();
[DllImport(dllPath)]
private static extern void Deinit();
[DllImport(dllPath)]
private static extern void LoadROM(byte* file, int fileSize);
[DllImport(dllPath, EntryPoint = "ResetCounters")]
private static extern void _ResetCounters();
[DllImport(dllPath)]
private static extern int GetFrameCount();
[DllImport(dllPath)]
private static extern void SetFrameCount(uint count);
[DllImport(dllPath)]
private static extern void FrameAdvance(uint buttons, byte touchX, byte touchY);
[CoreConstructor("NDS")]
public MelonDS(byte[] file, CoreComm comm, MelonSettings settings, MelonSyncSettings syncSettings)
{
_serviceProvider = new BasicServiceProvider(this);
ControllerDefinition = new ControllerDefinition { Name = "NDS Controller" };
ControllerDefinition.BoolButtons.Add("Left");
ControllerDefinition.BoolButtons.Add("Right");
ControllerDefinition.BoolButtons.Add("Up");
ControllerDefinition.BoolButtons.Add("Down");
ControllerDefinition.BoolButtons.Add("A");
ControllerDefinition.BoolButtons.Add("B");
ControllerDefinition.BoolButtons.Add("X");
ControllerDefinition.BoolButtons.Add("Y");
ControllerDefinition.BoolButtons.Add("L");
ControllerDefinition.BoolButtons.Add("R");
ControllerDefinition.BoolButtons.Add("Start");
ControllerDefinition.BoolButtons.Add("Select");
ControllerDefinition.BoolButtons.Add("LidOpen");
ControllerDefinition.BoolButtons.Add("LidClose");
ControllerDefinition.BoolButtons.Add("Power");
ControllerDefinition.BoolButtons.Add("Touch");
ControllerDefinition.AddXYPair("Touch{0}", AxisPairOrientation.RightAndUp, 0.RangeTo(255), 128, 0.RangeTo(191), 96); //TODO verify direction against hardware
CoreComm = comm;
_resampler = new SpeexResampler(SpeexResampler.Quality.QUALITY_DEFAULT, 32768, 44100, 32768, 44100);
SetUpFiles();
PutSettings(settings as MelonSettings);
PutSyncSettings(syncSettings as MelonSyncSettings);
if (!Init())
throw new Exception("Failed to init NDS.");
InitMemoryDomains();
fixed (byte* f = file)
{
LoadROM(f, file.Length);
}
}
/// <summary>
/// MelonDS expects bios and firmware files at a specific location.
/// This should never be called without an accompanying call to PutSyncSettings.
/// </summary>
private void SetUpFiles()
{
Directory.CreateDirectory("melon");
byte[] fwBytes;
bool missingAny = false;
fwBytes = CoreComm.CoreFileProvider.GetFirmware(new("NDS", "bios7"));
if (fwBytes != null)
File.WriteAllBytes("melon/bios7.bin", fwBytes);
else
{
File.Delete("melon/bios7.bin");
missingAny = true;
}
fwBytes = CoreComm.CoreFileProvider.GetFirmware(new("NDS", "bios9"));
if (fwBytes != null)
File.WriteAllBytes("melon/bios9.bin", fwBytes);
else
{
File.Delete("melon/bios9.bin");
missingAny = true;
}
fwBytes = CoreComm.CoreFileProvider.GetFirmware(new("NDS", "firmware"));
if (fwBytes != null)
File.WriteAllBytes("melon/firmware.bin", fwBytes);
else
{
File.Delete("melon/firmware.bin");
missingAny = true;
}
if (missingAny)
CoreComm.Notify("NDS bios and firmware files are recommended; at least one is missing.");
}
/// <summary>
/// Creates a modified copy of the given firmware file, with the user settings erased.
/// </summary>
/// <returns>Returns a path to the new file.</returns>
public static string CreateModifiedFirmware(string firmwarePath)
{
Directory.CreateDirectory("melon");
const string newPath = "melon/tohash.bin";
byte[] bytes = File.ReadAllBytes(firmwarePath);
// There are two regions for user settings
int settingsLength = GetUserSettingsLength();
for (int i = bytes.Length - 0x200; i < bytes.Length - 0x200 + settingsLength; i++)
bytes[i] = 0xFF;
for (int i = bytes.Length - 0x100; i < bytes.Length - 0x100 + settingsLength; i++)
bytes[i] = 0xFF;
File.WriteAllBytes(newPath, bytes);
return newPath;
}
}
}
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Waterbox;
using System;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
[PortedCore(CoreNames.MelonDS, "Arisotura", "0.9.3", "http://melonds.kuribo64.net/", singleInstance: true, isReleased: false)]
[ServiceNotApplicable(new[] { typeof(IDriveLight), typeof(IRegionable) })]
public partial class NDS : WaterboxCore
{
private readonly LibMelonDS _core;
private readonly NDSDisassembler _disassembler;
private SpeexResampler _resampler;
[CoreConstructor("NDS")]
public NDS(CoreLoadParameters<Settings, SyncSettings> lp)
: base(lp.Comm, new Configuration
{
DefaultWidth = 256,
DefaultHeight = 384,
MaxWidth = 256,
MaxHeight = 384,
MaxSamples = 1024,
DefaultFpsNumerator = 33513982,
DefaultFpsDenominator = 560190,
SystemId = "NDS"
})
{
var roms = lp.Roms.Select(r => r.RomData).ToList();
if (roms.Count > 3)
{
throw new InvalidOperationException("Wrong number of ROMs!");
}
bool gbacartpresent = roms.Count > 1;
_tracecb = MakeTrace;
_core = PreInit<LibMelonDS>(new WaterboxOptions
{
Filename = "melonDS.wbx",
SbrkHeapSizeKB = 2 * 1024,
SealedHeapSizeKB = 4,
InvisibleHeapSizeKB = 4,
PlainHeapSizeKB = 4,
MmapHeapSizeKB = 512 * 1024,
SkipCoreConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
SkipMemoryConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
}, new Delegate[] { _tracecb });
_syncSettings = lp.SyncSettings ?? new SyncSettings();
_settings = lp.Settings ?? new Settings();
var bios7 = _syncSettings.UseRealBIOS
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios7"))
: null;
var bios9 = _syncSettings.UseRealBIOS
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios9"))
: null;
var fw = CoreComm.CoreFileProvider.GetFirmware(new("NDS", "firmware"));
bool skipfw = _syncSettings.SkipFirmware || !_syncSettings.UseRealBIOS || fw == null;
LibMelonDS.LoadFlags flags = LibMelonDS.LoadFlags.NONE;
if (_syncSettings.UseRealBIOS)
flags |= LibMelonDS.LoadFlags.USE_REAL_BIOS;
if (skipfw)
flags |= LibMelonDS.LoadFlags.SKIP_FIRMWARE;
if (gbacartpresent)
flags |= LibMelonDS.LoadFlags.GBA_CART_PRESENT;
if (_settings.AccurateAudioBitrate)
flags |= LibMelonDS.LoadFlags.ACCURATE_AUDIO_BITRATE;
if (_syncSettings.FirmwareOverride || lp.DeterministicEmulationRequested)
flags |= LibMelonDS.LoadFlags.FIRMWARE_OVERRIDE;
var fwSettings = new LibMelonDS.FirmwareSettings();
var name = Encoding.UTF8.GetBytes(_syncSettings.FirmwareUsername);
fwSettings.FirmwareUsernameLength = name.Length;
fwSettings.FirmwareLanguage = _syncSettings.FirmwareLanguage;
if (_syncSettings.FirmwareStartUp == SyncSettings.StartUp.AutoBoot) fwSettings.FirmwareLanguage |= (SyncSettings.Language)0x40;
fwSettings.FirmwareBirthdayMonth = _syncSettings.FirmwareBirthdayMonth;
fwSettings.FirmwareBirthdayDay = _syncSettings.FirmwareBirthdayDay;
fwSettings.FirmwareFavouriteColour = _syncSettings.FirmwareFavouriteColour;
var message = Encoding.UTF8.GetBytes(_syncSettings.FirmwareMessage);
fwSettings.FirmwareMessageLength = message.Length;
_exe.AddReadonlyFile(roms[0], "game.rom");
if (gbacartpresent)
{
_exe.AddReadonlyFile(roms[1], "gba.rom");
if (roms[2] != null)
{
_exe.AddReadonlyFile(roms[2], "gba.ram");
}
}
if (_syncSettings.UseRealBIOS)
{
_exe.AddReadonlyFile(bios7, "bios7.rom");
_exe.AddReadonlyFile(bios9, "bios9.rom");
}
if (fw != null)
{
if (NDSFirmware.MaybeWarnIfBadFw(fw, CoreComm))
{
if (_syncSettings.FirmwareOverride || lp.DeterministicEmulationRequested)
{
NDSFirmware.SanitizeFw(fw);
}
}
_exe.AddReadonlyFile(fw, "firmware.bin");
}
unsafe
{
fixed (byte* namePtr = &name[0], messagePtr = &message[0])
{
fwSettings.FirmwareUsername = (IntPtr)namePtr;
fwSettings.FirmwareMessage = (IntPtr)messagePtr;
if (!_core.Init(flags, fwSettings))
{
throw new InvalidOperationException("Init returned false!");
}
}
}
_exe.RemoveReadonlyFile("game.rom");
if (gbacartpresent)
{
_exe.RemoveReadonlyFile("gba.rom");
if (roms[2] != null)
{
_exe.RemoveReadonlyFile("gba.ram");
}
}
if (_syncSettings.UseRealBIOS)
{
_exe.RemoveReadonlyFile("bios7.rom");
_exe.RemoveReadonlyFile("bios9.rom");
}
if (fw != null)
{
_exe.RemoveReadonlyFile("firmware.bin");
}
PostInit();
((MemoryDomainList)this.AsMemoryDomains()).SystemBus = new NDSSystemBus(this.AsMemoryDomains()["ARM9 System Bus"], this.AsMemoryDomains()["ARM7 System Bus"]);
DeterministicEmulation = lp.DeterministicEmulationRequested || (!_syncSettings.UseRealTime);
InitializeRtc(_syncSettings.InitialTime);
_resampler = new SpeexResampler(SpeexResampler.Quality.QUALITY_DEFAULT, 32768, 44100, 32768, 44100, null, this);
_serviceProvider.Register<ISoundProvider>(_resampler);
_disassembler = new NDSDisassembler();
_serviceProvider.Register<IDisassemblable>(_disassembler);
const string TRACE_HEADER = "ARM9+ARM7: PC, opcode, registers (r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, Cy, CpuMode)";
Tracer = new TraceBuffer(TRACE_HEADER);
_serviceProvider.Register<ITraceable>(Tracer);
}
public override void Dispose()
{
base.Dispose();
if (_resampler != null)
{
_resampler.Dispose();
_resampler = null;
}
}
public override ControllerDefinition ControllerDefinition => NDSController;
public static readonly ControllerDefinition NDSController = new ControllerDefinition
{
Name = "NDS Controller",
BoolButtons =
{
"Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Y", "X", "L", "R", "LidOpen", "LidClose", "Touch", "Power"
}
}.AddXYPair("Touch {0}", AxisPairOrientation.RightAndUp, 0.RangeTo(255), 128, 0.RangeTo(191), 96)
.AddAxis("Mic Input", (-1).RangeTo(2047), 0)
.AddAxis("GBA Light Sensor", 0.RangeTo(10), 0);
private LibMelonDS.Buttons GetButtons(IController c)
{
LibMelonDS.Buttons b = 0;
if (c.IsPressed("Up"))
b |= LibMelonDS.Buttons.UP;
if (c.IsPressed("Down"))
b |= LibMelonDS.Buttons.DOWN;
if (c.IsPressed("Left"))
b |= LibMelonDS.Buttons.LEFT;
if (c.IsPressed("Right"))
b |= LibMelonDS.Buttons.RIGHT;
if (c.IsPressed("Start"))
b |= LibMelonDS.Buttons.START;
if (c.IsPressed("Select"))
b |= LibMelonDS.Buttons.SELECT;
if (c.IsPressed("B"))
b |= LibMelonDS.Buttons.B;
if (c.IsPressed("A"))
b |= LibMelonDS.Buttons.A;
if (c.IsPressed("Y"))
b |= LibMelonDS.Buttons.Y;
if (c.IsPressed("X"))
b |= LibMelonDS.Buttons.X;
if (c.IsPressed("L"))
b |= LibMelonDS.Buttons.L;
if (c.IsPressed("R"))
b |= LibMelonDS.Buttons.R;
if (c.IsPressed("LidOpen"))
b |= LibMelonDS.Buttons.LIDOPEN;
if (c.IsPressed("LidClose"))
b |= LibMelonDS.Buttons.LIDCLOSE;
if (c.IsPressed("Touch"))
b |= LibMelonDS.Buttons.TOUCH;
if (c.IsPressed("Power"))
b |= LibMelonDS.Buttons.POWER;
return b;
}
private bool _renderSound;
protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound)
{
_renderSound = rendersound;
_core.SetTraceCallback(Tracer.IsEnabled() ? _tracecb : null);
return new LibMelonDS.FrameInfo
{
Time = GetRtcTime(!DeterministicEmulation),
Keys = GetButtons(controller),
TouchX = (byte)controller.AxisValue("Touch X"),
TouchY = (byte)controller.AxisValue("Touch Y"),
MicInput = (short)controller.AxisValue("Mic Input"),
GBALightSensor = (byte)controller.AxisValue("GBA Light Sensor"),
};
}
protected override void FrameAdvancePost()
{
// the core SHOULD produce 547 or 548 samples each frame
// however, it seems in some cases (first few frames on power on and lid closed) it doesn't for some reason
// hack around it here
if (_numSamples < 547 && _renderSound)
{
for (int i = _numSamples * 2; i < (547 * 2); i++)
{
_soundBuffer[i] = 0;
}
_numSamples = 547;
}
}
// omega hack
public class NDSSystemBus : MemoryDomain
{
private readonly MemoryDomain Arm9Bus;
private readonly MemoryDomain Arm7Bus;
public NDSSystemBus(MemoryDomain arm9, MemoryDomain arm7)
{
Name = "System Bus";
Size = 1L << 32;
WordSize = 4;
EndianType = Endian.Little;
Writable = false;
Arm9Bus = arm9;
Arm7Bus = arm7;
}
public bool UseArm9 { get; set; } = true;
public override byte PeekByte(long addr) => UseArm9 ? Arm9Bus.PeekByte(addr) : Arm7Bus.PeekByte(addr);
public override void PokeByte(long addr, byte val) => throw new InvalidOperationException();
}
}
}

View File

@ -1,36 +0,0 @@
using System;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
partial class MelonDS : IInputPollable
{
public int LagCount
{
get => GetLagFrameCount();
set => SetLagFrameCount((uint)value);
}
public bool IsLagFrame
{
get => _IsLagFrame();
set => SetIsLagFrame(value);
}
public IInputCallbackSystem InputCallbacks => throw new NotImplementedException();
[DllImport(dllPath, EntryPoint = "IsLagFrame")]
private static extern bool _IsLagFrame();
[DllImport(dllPath)]
private static extern int GetLagFrameCount();
[DllImport(dllPath)]
private static extern void SetIsLagFrame(bool isLag);
[DllImport(dllPath)]
private static extern void SetLagFrameCount(uint count);
}
}

View File

@ -1,120 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : IMemoryDomains
{
private SortedList<string, MemoryDomain> domains;
private void InitMemoryDomains()
{
MainMemory = new MemoryDomainIntPtr("RAM", MemoryDomain.Endian.Little, (IntPtr)GetMainMemory(), GetMainMemorySize(), true, 4);
SystemBus = new MelonSystemBus();
domains = new SortedList<string, MemoryDomain>
{
{ "RAM", MainMemory },
{ "System Bus", SystemBus }
};
}
public MemoryDomain this[string name] => domains[name];
public bool Has(string name)
{
return domains.ContainsKey(name);
}
public MemoryDomain MainMemory { get; private set; }
[DllImport(dllPath)]
private static extern byte* GetMainMemory();
[DllImport(dllPath)]
private static extern int GetMainMemorySize();
// NDS has two CPUs, with different memory mappings.
public bool HasSystemBus => true;
public MemoryDomain SystemBus { get; private set; }
private class MelonSystemBus : MemoryDomain
{
[DllImport(dllPath)]
private static extern byte ARM9Read8(uint addr);
[DllImport(dllPath)]
private static extern void ARM9Write8(uint addr, byte value);
[DllImport(dllPath)]
private static extern ushort ARM9Read16(uint addr);
[DllImport(dllPath)]
private static extern void ARM9Write16(uint addr, ushort value);
[DllImport(dllPath)]
private static extern uint ARM9Read32(uint addr);
[DllImport(dllPath)]
private static extern void ARM9Write32(uint addr, uint value);
public MelonSystemBus()
{
Name = "System Bus";
Size = 0x0B00_0000;
WordSize = 4;
EndianType = Endian.Little;
Writable = true;
}
public override byte PeekByte(long addr)
{
return ARM9Read8((uint)addr);
}
public override void PokeByte(long addr, byte val)
{
ARM9Write8((uint)addr, val);
}
public override ushort PeekUshort(long addr, bool bigEndian)
{
ushort ret = ARM9Read16((uint)addr);
if (bigEndian)
ret = SwapEndianness(ret);
return ret;
}
public override void PokeUshort(long addr, ushort val, bool bigEndian)
{
if (bigEndian)
val = SwapEndianness(val);
ARM9Write16((uint)addr, val);
}
public override uint PeekUint(long addr, bool bigEndian)
{
uint ret = ARM9Read32((uint)addr);
if (bigEndian)
ret = SwapEndianness(ret);
return ret;
}
public override void PokeUint(long addr, uint val, bool bigEndian)
{
if (bigEndian)
val = SwapEndianness(val);
ARM9Write32((uint)addr, val);
}
}
public IEnumerator<MemoryDomain> GetEnumerator()
{
return domains.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public static ushort SwapEndianness(ushort value)
{
return (ushort)((value >> 8) | (value << 8));
}
public static uint SwapEndianness(uint value)
{
return (value >> 24) | ((value & 0x00ff0000) >> 8) | ((value & 0x0000ff00) << 8) | (value << 24);
}
}
}

View File

@ -1,39 +0,0 @@
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : ISaveRam
{
public bool SaveRamModified => IsSRAMModified();
public byte[] CloneSaveRam()
{
int length = GetSRAMLength();
byte[] data = new byte[length];
fixed (byte* dst = data)
{
GetSRAM(dst, length);
}
return data;
}
public void StoreSaveRam(byte[] data)
{
fixed (byte* src = data)
{
SetSRAM(src, data.Length);
}
}
[DllImport(dllPath)]
private static extern int GetSRAMLength();
[DllImport(dllPath)]
private static extern bool IsSRAMModified();
[DllImport(dllPath)]
private static extern void GetSRAM(byte* dst, int length);
[DllImport(dllPath)]
private static extern void SetSRAM(byte* src, int length);
}
}

View File

@ -1,199 +0,0 @@
using System;
using System.ComponentModel;
using System.Text;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
using Newtonsoft.Json;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : ISettable<MelonDS.MelonSettings, MelonDS.MelonSyncSettings>
{
private MelonSettings _settings = new MelonSettings();
public MelonSettings GetSettings() => _settings.Clone();
public MelonSyncSettings GetSyncSettings()
{
var ret = new MelonSyncSettings();
fixed (byte* ptr = ret.UserSettings)
{
if (!GetUserSettings(ptr))
{
return null;
}
}
ret.BootToFirmware = !GetDirectBoot();
ret.TimeAtBoot = GetTimeAtBoot();
return ret;
}
public PutSettingsDirtyBits PutSettings(MelonSettings o)
{
bool screenChanged = false;
if (o != null)
{
screenChanged |= _settings.ScaleFactor != o.ScaleFactor;
screenChanged |= _settings.ScreenGap != o.ScreenGap;
screenChanged |= _settings.ScreenLayout != o.ScreenLayout;
screenChanged |= _settings.ScreenRotation != o.ScreenRotation;
}
_settings = o ?? new MelonSettings();
SetScaleFactor(_settings.ScaleFactor);
return screenChanged ? PutSettingsDirtyBits.None : PutSettingsDirtyBits.None;
}
public PutSettingsDirtyBits PutSyncSettings(MelonSyncSettings o)
{
if (o == null)
{
o = new MelonSyncSettings();
SetUserSettings(null);
}
else
{
fixed (byte* ptr = o.UserSettings)
SetUserSettings(ptr);
}
SetDirectBoot(!o.BootToFirmware);
SetTimeAtBoot(o.TimeAtBoot);
// At present, no sync settings can be modified without requiring a reboot.
return PutSettingsDirtyBits.RebootCore;
}
[DllImport(dllPath)]
private static extern bool GetUserSettings(byte* dst);
[DllImport(dllPath)]
private static extern int GetUserSettingsLength();
private static readonly int UserSettingsLength = GetUserSettingsLength();
[DllImport(dllPath)]
private static extern void SetUserSettings(byte* src);
[DllImport(dllPath)]
private static extern bool GetDirectBoot();
[DllImport(dllPath)]
private static extern void SetDirectBoot(bool value);
[DllImport(dllPath)]
private static extern void SetTimeAtBoot(uint value);
[DllImport(dllPath)]
private static extern uint GetTimeAtBoot();
[DllImport(dllPath)]
private static extern uint GetScaleFactor();
[DllImport(dllPath)]
private static extern void SetScaleFactor(uint value);
public enum ScreenLayoutKind
{
Vertical, Horizontal, Top, Bottom
}
public enum ScreenRotationKind
{
Rotate0, Rotate90, Rotate180, Rotate270
}
public class MelonSettings
{
public MelonSettings Clone() => (MelonSettings)MemberwiseClone();
[DisplayName("Screen Layout")]
[Description("Adjusts the layout of the screens")]
public ScreenLayoutKind ScreenLayout { get; set; } = ScreenLayoutKind.Vertical;
[DisplayName("Rotation")]
[Description("Adjusts the orientation of the screens")]
public ScreenRotationKind ScreenRotation { get; set; } = ScreenRotationKind.Rotate0;
[DisplayName("Screen Gap")]
public int ScreenGap { get; set; }
[DisplayName("Scale Factor")]
public uint ScaleFactor { get; set; } = 1;
}
public class MelonSyncSettings
{
public MelonSyncSettings()
{
UserSettings = new byte[UserSettingsLength];
}
public MelonSyncSettings Clone() => (MelonSyncSettings)MemberwiseClone();
public bool BootToFirmware { get; set; }
public uint TimeAtBoot { get; set; } = 946684800; // 2000-01-01 00:00:00 (earliest date possible on a DS)
[JsonProperty]
internal byte[] UserSettings;
[JsonIgnore]
public byte FavoriteColor
{
get => UserSettings[2];
set => UserSettings[2] = value;
}
[JsonIgnore]
public byte BirthdayMonth
{
get => UserSettings[3];
set => UserSettings[3] = value;
}
[JsonIgnore]
public byte BirthdayDay
{
get => UserSettings[4];
set => UserSettings[4] = value;
}
private const int MaxNicknameLength = 10;
[JsonIgnore]
public string Nickname
{
get
{
fixed (byte* ptr = UserSettings)
return Encoding.Unicode.GetString(ptr + 6, NicknameLength * 2);
}
set
{
if (value.Length > MaxNicknameLength) value = value.Substring(0, MaxNicknameLength);
byte[] nick = new byte[MaxNicknameLength * 2 + 2];
// I do not know how an actual NDS would handle characters that require more than 2 bytes to encode.
// They can't be input normally, so I will ignore attempts to set a nickname that uses them.
if (Encoding.Unicode.GetBytes(value, 0, value.Length, nick, 0) != value.Length * 2)
{
return;
}
// The extra 2 bytes on the end will overwrite nickname length, which is set immediately after
nick.CopyTo(UserSettings, 6);
UserSettings[0x1A] = (byte)value.Length;
}
}
[JsonIgnore]
public short NicknameLength => UserSettings[0x1A];
[JsonIgnore]
public long RtcOffset
{
get => BitConverter.ToInt64(UserSettings, 0x68);
set => BitConverter.GetBytes(value).CopyTo(UserSettings, 0x68);
}
}
}
}

View File

@ -1,52 +0,0 @@
using System;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : ISoundProvider
{
private readonly SpeexResampler _resampler;
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
public void DiscardSamples()
{
_DiscardSamples();
}
public void GetSamplesAsync(short[] samples)
{
throw new InvalidOperationException();
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
nsamp = GetSampleCount();
samples = new short[nsamp * 2]; //*2 for stereo sound
fixed (short* data = samples)
{
GetSamples(data, nsamp);
}
// BizHawk requires a sample rate of 44100 Hz.
_resampler.EnqueueSamples(samples, nsamp);
_resampler.GetSamplesSync(out samples, out nsamp);
}
public void SetSyncMode(SyncSoundMode mode)
{
if (mode == SyncSoundMode.Async)
throw new InvalidOperationException();
}
[DllImport(dllPath)]
private static extern int GetSampleCount();
[DllImport(dllPath)]
private static extern void GetSamples(short* data, int count);
[DllImport(dllPath, EntryPoint = "DiscardSamples")]
private static extern void _DiscardSamples();
}
}

View File

@ -1,55 +0,0 @@
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
using System.IO;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : IStatable
{
private byte[] _stateBuffer = new byte[0];
public void LoadStateBinary(BinaryReader reader)
{
var mStream = new MemoryStream();
reader.BaseStream.CopyTo(mStream);
LoadStateByteArray(mStream.GetBuffer(), (int)mStream.Length);
}
private void LoadStateByteArray(byte[] data, int length = -1)
{
if (length == -1) length = data.Length;
fixed (byte* ptr = data)
{
if (!UseSavestate(ptr, length))
CoreComm.Notify("Savestate load failed! See log window for details.");
}
}
public void SaveStateBinary(BinaryWriter writer)
{
int len = GetSavestateSize();
if (len > _stateBuffer.Length)
{
_stateBuffer = new byte[len];
}
fixed (byte* ptr = _stateBuffer)
{
GetSavestateData(ptr, len);
}
writer.Write(_stateBuffer, 0, len);
}
[DllImport(dllPath)]
private static extern bool UseSavestate(byte* data, int len);
[DllImport(dllPath)]
private static extern int GetSavestateSize();
[DllImport(dllPath)]
private static extern void GetSavestateData(byte* data, int size);
}
}

View File

@ -1,54 +0,0 @@
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : IVideoProvider
{
public int VirtualWidth => 256;
public int VirtualHeight => 384;
public int BufferWidth => 256;
public int BufferHeight => 384;
public int VsyncNumerator => 60;
public int VsyncDenominator => 1;
public int BackgroundColor => 0;
[DllImport(dllPath)]
private static extern int* GetTopScreenBuffer();
[DllImport(dllPath)]
private static extern int* GetBottomScreenBuffer();
// BizHawk needs to be able to modify the buffer when loading savestates.
private const int SingleScreenLength = 256 * 192;
private readonly int[] _buffer = new int[256 * 192 * 2];
private bool _getNewBuffer = true;
public int[] GetVideoBuffer()
{
if (_getNewBuffer)
{
_getNewBuffer = false;
PopulateBuffer();
}
return _buffer;
}
private void PopulateBuffer()
{
var top = GetTopScreenBuffer();
var bottom = GetBottomScreenBuffer();
for (var i = 0; i < SingleScreenLength; i++)
{
_buffer[i] = top[i];
_buffer[SingleScreenLength + i] = bottom[i];
}
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using BizHawk.BizInvoke;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.ARM;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
public class NDSDisassembler : VerifiedDisassembler
{
private static readonly Darm _libdarm = BizInvoker.GetInvoker<Darm>(
new DynamicLibraryImportResolver(OSTailoredCode.IsUnixHost ? "libdarm.so" : "libdarm.dll", hasLimitedLifetime: false),
CallingConventionAdapters.Native
);
public override IEnumerable<string> AvailableCpus => new[]
{
"ARM v5",
"ARM v5 (Thumb)",
"ARM v4",
"ARM v4 (Thumb)",
};
public override string PCRegisterName => int.Parse(Cpu.Substring(5, 1)) == 5 ? "ARM9 r15" : "ARM7 r15";
public override string Disassemble(MemoryDomain m, uint addr, out int length)
{
if (m is NDS.NDSSystemBus NdsSysBus)
{
NdsSysBus.UseArm9 = int.Parse(Cpu.Substring(5, 1)) == 5;
m = NdsSysBus;
}
if (Cpu.Length == 14)
{
addr &= ~1u;
int op = m.PeekByte(addr) | m.PeekByte(addr + 1) << 8;
string ret = _libdarm.DisassembleStuff(addr | 1, (uint)op);
length = 2;
return ret;
}
else
{
addr &= ~3u;
int op = m.PeekByte(addr)
| m.PeekByte(addr + 1) << 8
| m.PeekByte(addr + 2) << 16
| m.PeekByte(addr + 3) << 24;
string ret = _libdarm.DisassembleStuff(addr, (uint)op);
length = 4;
return ret;
}
}
public string Trace(uint pc, uint op, bool isthumb)
{
if (isthumb)
{
pc &= ~1u;
string ret = _libdarm.DisassembleStuff(pc | 1, op);
if (ret == null)
{
ret = "Can't disassemble???";
}
return ret;
}
else
{
pc &= ~3u;
string ret = _libdarm.DisassembleStuff(pc, op);
if (ret == null)
{
ret = "Can't disassemble???";
}
return ret;
}
}
}
}

View File

@ -0,0 +1,205 @@
using BizHawk.Common.BufferExtensions;
using BizHawk.Emulation.Common;
using System;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
// mostly a c++ -> c# port of melon's firmware verification code
public class NDSFirmware
{
public static bool MaybeWarnIfBadFw(byte[] fw, CoreComm comm)
{
if (fw.Length != 0x20000 && fw.Length != 0x40000 && fw.Length != 0x80000)
{
comm.ShowMessage("Bad firmware length detected! Firmware might not work!");
return false;
}
if (fw[0x17C] != 0xFF)
{
comm.ShowMessage("Hacked firmware detected! Firmware might not work!");
return false;
}
int fwMask = fw.Length - 1;
string badCrc16s = "";
if (!VerifyCrc16(fw, 0x2C, (fw[0x2C + 1] << 8) | fw[0x2C], 0x0000, 0x2A))
{
badCrc16s += " Wifi ";
}
if (!VerifyCrc16(fw, 0x7FA00 & fwMask, 0xFE, 0x0000, 0x7FAFE & fwMask))
{
badCrc16s += " AP1 ";
}
if (!VerifyCrc16(fw, 0x7FB00 & fwMask, 0xFE, 0x0000, 0x7FBFE & fwMask))
{
badCrc16s += " AP2 ";
}
if (!VerifyCrc16(fw, 0x7FC00 & fwMask, 0xFE, 0x0000, 0x7FCFE & fwMask))
{
badCrc16s += " AP3 ";
}
if (!VerifyCrc16(fw, 0x7FE00 & fwMask, 0x70, 0xFFFF, 0x7FE72 & fwMask))
{
badCrc16s += " USER0 ";
}
if (!VerifyCrc16(fw, 0x7FF00 & fwMask, 0x70, 0xFFFF, 0x7FF72 & fwMask))
{
badCrc16s += " USER1 ";
}
if (badCrc16s != "")
{
comm.ShowMessage("Bad Firmware CRC16(s) detected! Firmware might not work! Bad CRC16(s): " + badCrc16s);
return false;
}
return CheckDecryptedCodeChecksum(fw, comm);
}
public static void SanitizeFw(byte[] fw)
{
int fwMask = fw.Length - 1;
int[] apstart = new int[3] { 0x07FA00 & fwMask, 0x07FB00 & fwMask, 0x07FC00 & fwMask };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 0x100; j++)
{
fw[apstart[i] + j] = 0;
}
}
// gbatek marks these as unknown, they seem to depend on the mac address???
// bytes 4 (upper nibble only) and 5 also seem to be just random?
// various combinations noted (noting last 2 bytes are crc16)
// F8 98 C1 E6 CC DD A9 E1 85 D4 9B
// F8 98 C1 E6 CC 1D 66 E1 85 D8 A4
// F8 98 C1 E6 CC 9D 6B E1 85 60 A7
// F8 98 C1 E6 CC 5D 92 E1 85 8C 96
// different mac address
// 18 90 15 E9 7C 1D F1 E1 85 74 02
byte[] macdependentbytes = new byte[11] { 0xF8, 0x98, 0xC1, 0xE6, 0xCC, 0x9D, 0xBE, 0xE1, 0x85, 0x71, 0x5F };
int apoffset = 0xF5;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 11; j++)
{
fw[apstart[i] + apoffset + j] = macdependentbytes[j];
}
}
int ffoffset = 0xE7;
for (int i = 0; i < 3; i++)
{
fw[apstart[i] + ffoffset] = 0xFF;
}
// slot 3 doesn't have those mac dependent bytes???
fw[apstart[2] + 0xFE] = 0x0A;
fw[apstart[2] + 0xFF] = 0xF0;
int[] usersettings = new int[2] { 0x7FE00 & fwMask, 0x7FF00 & fwMask };
for (int i = 0; i < 2; i++)
{
unsafe
{
fixed (byte* us = &fw[usersettings[i]])
{
// alarm settings
us[0x52] = 0;
us[0x53] = 0;
us[0x56] = 0;
// year of first boot
us[0x66] = 0;
// rtc offset
us[0x68] = 0;
us[0x69] = 0;
us[0x6A] = 0;
us[0x6B] = 0;
// update counter
us[0x70] = 0;
us[0x71] = 0;
// fix crc16 (probably redundant)
ushort crc16 = Crc16(us, 0x70, 0xFFFF);
us[0x72] = (byte)(crc16 & 0xFF);
us[0x73] = (byte)(crc16 >> 8);
}
}
}
}
private static unsafe ushort Crc16(byte* data, int len, int seed)
{
var poly = new ushort[8] { 0xC0C1, 0xC181, 0xC301, 0xC601, 0xCC01, 0xD801, 0xF001, 0xA001 };
for (int i = 0; i < len; i++)
{
seed ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((seed & 0x1) != 0)
{
seed >>= 1;
seed ^= (poly[j] << (7 - j));
}
else
{
seed >>= 1;
}
}
}
return (ushort)(seed & 0xFFFF);
}
private static unsafe bool VerifyCrc16(byte[] fw, int startaddr, int len, int seed, int crcaddr)
{
ushort storedCrc16 = (ushort)((fw[crcaddr + 1] << 8) | fw[crcaddr]);
fixed (byte* start = &fw[startaddr])
{
ushort actualCrc16 = Crc16(start, len, seed);
return storedCrc16 == actualCrc16;
}
}
[DllImport("libfwunpack", CallingConvention = CallingConvention.Cdecl)]
private static extern bool GetDecryptedFirmware(byte[] fw, int fwlen, out IntPtr decryptedFw, out int decryptedlen);
[DllImport("libfwunpack", CallingConvention = CallingConvention.Cdecl)]
private static extern void FreeDecryptedFirmware(IntPtr decryptedFw);
private static string[] goodhashes = new string[]
{
"D83861C66796665A9777B4E9078E9CC8EB13D880", // MACP nds (one of v1-v4), supposedly the most common
"F87038265D24677419FE0AF9EED63B4CE1378CC9", // MACg nds (v5)
"674639373F16539F718C728D6CA0C83A2DB66770", // MACh nds-lite (v6)
};
private static bool CheckDecryptedCodeChecksum(byte[] fw, CoreComm comm)
{
if (!GetDecryptedFirmware(fw, fw.Length, out IntPtr decryptedfw, out int decrypedfwlen))
{
comm.ShowMessage("Firmware could not be decryped for verification! This firmware might be not work!");
return false;
}
else
{
byte[] DecryptedFirmware = new byte[decrypedfwlen];
Marshal.Copy(decryptedfw, DecryptedFirmware, 0, decrypedfwlen);
FreeDecryptedFirmware(decryptedfw);
var hash = BufferExtensions.HashSHA1(DecryptedFirmware, 0, decrypedfwlen);
if (hash != goodhashes[0] && hash != goodhashes[1] && hash != goodhashes[2])
{
comm.ShowMessage("Potentially bad firmware dump! Decrypted hash " + hash + " does not match known good dumps.");
return false;
}
}
return true;
}
}
}

View File

@ -210,6 +210,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
Frame++;
if (IsLagFrame = frame.Lagged != 0)
LagCount++;
CycleCount += frame.Cycles;
AdvanceRtc();
if (render)
@ -247,10 +248,14 @@ namespace BizHawk.Emulation.Cores.Waterbox
public int Frame { get; private set; }
public int LagCount { get; set; }
public bool IsLagFrame { get; set; }
public long CycleCount { get; private set; }
public void ResetCounters()
{
Frame = 0;
LagCount = 0;
IsLagFrame = false;
CycleCount = 0;
}
protected readonly BasicServiceProvider _serviceProvider;
@ -269,6 +274,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
Frame = reader.ReadInt32();
LagCount = reader.ReadInt32();
IsLagFrame = reader.ReadBoolean();
CycleCount = reader.ReadInt64();
BufferWidth = reader.ReadInt32();
BufferHeight = reader.ReadInt32();
_clockTime = reader.ReadInt64();
@ -289,6 +295,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
writer.Write(Frame);
writer.Write(LagCount);
writer.Write(IsLagFrame);
writer.Write(CycleCount);
writer.Write(BufferWidth);
writer.Write(BufferHeight);
writer.Write(_clockTime);

View File

@ -186,9 +186,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <summary>
/// Mounts a file in the environment. All data will be immediately consumed from the reader, which will not be used after this call.
/// To prevent nondeterminism, adding and removing files is very limited WRT savestates. If a file is writable, it must never exist
/// when save_state is called, and can only be used for transient operations. If a file is readable, it can appear in savestates,
/// but it must exist in every savestate and the exact sequence of add_file calls must be consistent from savestate to savestate.
/// To prevent nondeterminism, adding and removing files is very limited WRT savestates. Every file added must either exist
/// in every savestate, or never appear in any savestates. All savestateable files must be added in the same order for every run.
/// </summary>
[BizImport(CallingConvention.Cdecl)]
public abstract void wbx_mount_file(IntPtr /*WaterboxHost*/ obj, string name, ReadCallback reader, IntPtr userdata, bool writable, ReturnData /*void*/ ret);
@ -196,6 +195,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <summary>
/// Remove a file previously added. Writer is optional; if provided, the contents of the file at time of removal will be dumped to it.
/// It is an error to remove a file which is currently open in the guest.
/// If the file has been used in savestates, it does not make sense to remove it here, but nothing will stop you.
/// </summary>
[BizImport(CallingConvention.Cdecl)]
public abstract void wbx_unmount_file(IntPtr /*WaterboxHost*/ obj, string name, WriteCallback writer, IntPtr userdata, ReturnData /*void*/ ret);

View File

@ -36,7 +36,7 @@ namespace BizHawk.Emulation.Cores
new ButtonSchema(366, 86, "A"),
// Screen
new TargetedPairSchema(72, 35, "TouchX")
new TargetedPairSchema(72, 35, "Touch X")
{
TargetSize = new Size(256, 192)
},
@ -49,11 +49,25 @@ namespace BizHawk.Emulation.Cores
{
return new ConsoleSchema
{
Size = new Size(160, 45),
Buttons = new[]
Size = new Size(240, 260),
Buttons = new PadSchemaControl[]
{
new ButtonSchema(8, 18, "LidOpen") { DisplayName = "Lid Open" },
new ButtonSchema(68, 18, "LidClose") { DisplayName = "Lid Close" }
new ButtonSchema(68, 18, "LidClose") { DisplayName = "Lid Close" },
new ButtonSchema(128, 18, "Power"),
new SingleAxisSchema(10, 63, "Mic Input")
{
TargetSize = new Size(226, 69),
MinValue = -1,
MaxValue = 2047,
},
new SingleAxisSchema(10, 137, "GBA Light Sensor")
{
TargetSize = new Size(226, 69),
MaxValue = 10,
},
}
};
}

@ -0,0 +1 @@
Subproject commit acf90125257bced76f7a5112ec87d45127bcc34b

View File

@ -5,6 +5,7 @@ cd emulibc && make -f Makefile $1 -j && cd -
cd libco && make -f Makefile $1 -j && cd -
cd gpgx && make -f Makefile $1 -j && cd -
cd libsnes && make -f Makefile $1 -j && cd -
cd melon && make -f Makefile $1 -j && cd -
cd picodrive && make -f Makefile $1 -j && cd -
cd snes9x && make -f Makefile $1 -j && cd -
cd uzem && make -f Makefile $1 -j && cd -

View File

@ -0,0 +1,365 @@
#include "NDS.h"
#include "GPU.h"
#include "SPU.h"
#include "RTC.h"
#include "ARM.h"
#include "NDSCart.h"
#include "NDSCart_SRAMManager.h"
#include "GBACart.h"
#include "Platform.h"
#include "Config.h"
#include "types.h"
#include "frontend/mic_blow.h"
#include "../emulibc/emulibc.h"
#include "../emulibc/waterboxcore.h"
#include <algorithm>
#include <time.h>
#define EXPORT extern "C" ECL_EXPORT
static GPU::RenderSettings biz_render_settings { false, 1, false };
static bool biz_skip_fw;
static time_t biz_time;
static time_t BizRtcCallback()
{
return biz_time;
}
typedef enum
{
NONE = 0x00,
USE_REAL_BIOS = 0x01,
SKIP_FIRMWARE = 0x02,
GBA_CART_PRESENT = 0x04,
ACCURATE_AUDIO_BITRATE = 0x08,
FIRMWARE_OVERRIDE = 0x10,
} LoadFlags;
static const char* bios9_path = "bios9.rom";
static const char* bios7_path = "bios7.rom";
static const char* firmware_path = "firmware.bin";
static const char* rom_path = "game.rom";
static const char* sram_path = "save.ram";
static const char* gba_rom_path = "gba.rom";
static const char* gba_sram_path = "gba.ram";
static const char* no_path = "";
typedef struct
{
char* FirmwareUsername; // max 10 length (then terminator)
s32 FirmwareUsernameLength;
s32 FirmwareLanguage;
s32 FirmwareBirthdayMonth;
s32 FirmwareBirthdayDay;
s32 FirmwareFavouriteColour;
char* FirmwareMessage; // max 26 length (then terminator)
s32 FirmwareMessageLength;
} FirmwareSettings;
EXPORT bool Init(LoadFlags flags, FirmwareSettings* fwSettings)
{
Config::ExternalBIOSEnable = !!(flags & USE_REAL_BIOS);
strncpy(Config::BIOS9Path, Config::ExternalBIOSEnable ? bios9_path : no_path, 1023);
Config::BIOS9Path[1023] = '\0';
strncpy(Config::BIOS7Path, Config::ExternalBIOSEnable ? bios7_path : no_path, 1023);
Config::BIOS7Path[1023] = '\0';
strncpy(Config::FirmwarePath, firmware_path, 1023);
Config::FirmwarePath[1023] = '\0';
NDS::SetConsoleType(0);
// time calls are deterministic under wbx, so this will force the mac address to a constant value instead of relying on whatever is in the firmware
// fixme: might want to allow the user to specify mac address?
srand(time(NULL));
Config::RandomizeMAC = true;
Config::AudioBitrate = !!(flags & ACCURATE_AUDIO_BITRATE) ? 1 : 2;
Config::FixedBootTime = true;
Config::UseRealTime = false;
Config::TimeAtBoot = 0;
biz_time = 0;
RTC::RtcCallback = BizRtcCallback;
Config::FirmwareOverrideSettings = !!(flags & FIRMWARE_OVERRIDE);
if (Config::FirmwareOverrideSettings)
{
memcpy(Config::FirmwareUsername, fwSettings->FirmwareUsername, fwSettings->FirmwareUsernameLength);
Config::FirmwareUsername[fwSettings->FirmwareUsernameLength] = 0;
Config::FirmwareLanguage = fwSettings->FirmwareLanguage;
Config::FirmwareBirthdayMonth = fwSettings->FirmwareBirthdayMonth;
Config::FirmwareBirthdayDay = fwSettings->FirmwareBirthdayDay;
Config::FirmwareFavouriteColour = fwSettings->FirmwareFavouriteColour;
memcpy(Config::FirmwareMessage, fwSettings->FirmwareMessage, fwSettings->FirmwareMessageLength);
Config::FirmwareMessage[fwSettings->FirmwareMessageLength] = 0;
}
if (!NDS::Init()) return false;
GPU::InitRenderer(false);
GPU::SetRenderSettings(false, biz_render_settings);
biz_skip_fw = !!(flags & SKIP_FIRMWARE);
if (!NDS::LoadROM(rom_path, no_path, biz_skip_fw)) return false;
if (flags & GBA_CART_PRESENT) { if (!NDS::LoadGBAROM(gba_rom_path, gba_sram_path)) return false; }
Config::FirmwareOverrideSettings = false;
return true;
}
EXPORT void PutSaveRam(u8* data, u32 len)
{
if (NDSCart_SRAMManager::SecondaryBufferLength > 0)
NDSCart_SRAMManager::UpdateBuffer(data, len);
}
EXPORT void GetSaveRam(u8* data)
{
if (NDSCart_SRAMManager::SecondaryBufferLength > 0)
NDSCart_SRAMManager::FlushSecondaryBuffer(data, NDSCart_SRAMManager::SecondaryBufferLength);
}
EXPORT s32 GetSaveRamLength()
{
return NDSCart_SRAMManager::SecondaryBufferLength;
}
EXPORT bool SaveRamIsDirty()
{
return NDSCart_SRAMManager::NeedsFlush();
}
/* excerpted from gbatek
NDS9 Memory Map
00000000h Instruction TCM (32KB) (not moveable) (mirror-able to 1000000h)
0xxxx000h Data TCM (16KB) (moveable)
02000000h Main Memory (4MB)
03000000h Shared WRAM (0KB, 16KB, or 32KB can be allocated to ARM9)
04000000h ARM9-I/O Ports
05000000h Standard Palettes (2KB) (Engine A BG/OBJ, Engine B BG/OBJ)
06000000h VRAM - Engine A, BG VRAM (max 512KB)
06200000h VRAM - Engine B, BG VRAM (max 128KB)
06400000h VRAM - Engine A, OBJ VRAM (max 256KB)
06600000h VRAM - Engine B, OBJ VRAM (max 128KB)
06800000h VRAM - "LCDC"-allocated (max 656KB)
07000000h OAM (2KB) (Engine A, Engine B)
08000000h GBA Slot ROM (max 32MB)
0A000000h GBA Slot RAM (max 64KB)
FFFF0000h ARM9-BIOS (32KB) (only 3K used)
NDS7 Memory Map
00000000h ARM7-BIOS (16KB)
02000000h Main Memory (4MB)
03000000h Shared WRAM (0KB, 16KB, or 32KB can be allocated to ARM7)
03800000h ARM7-WRAM (64KB)
04000000h ARM7-I/O Ports
04800000h Wireless Communications Wait State 0 (8KB RAM at 4804000h)
04808000h Wireless Communications Wait State 1 (I/O Ports at 4808000h)
06000000h VRAM allocated as Work RAM to ARM7 (max 256K)
08000000h GBA Slot ROM (max 32MB)
0A000000h GBA Slot RAM (max 64KB)
Further Memory (not mapped to ARM9/ARM7 bus)
3D Engine Polygon RAM (52KBx2)
3D Engine Vertex RAM (72KBx2)
Firmware (256KB) (built-in serial flash memory)
GBA-BIOS (16KB) (not used in NDS mode)
NDS Slot ROM (serial 8bit-bus, max 4GB with default protocol)
NDS Slot FLASH/EEPROM/FRAM (serial 1bit-bus)
*/
template<bool arm9>
static bool SafeToPeek(u32 addr)
{
if (arm9)
{
switch (addr)
{
case 0x04000130:
case 0x04000131:
case 0x04000600:
case 0x04000601:
case 0x04000602:
case 0x04000603:
return false;
}
}
else // arm7
{
if (addr >= 0x04800000 && addr <= 0x04810000)
{
if (addr & 1) addr--;
addr &= 0x7FFE;
if (addr == 0x044 || addr == 0x060)
return false;
}
}
return true;
}
static void ARM9Access(u8* buffer, s64 address, s64 count, bool write)
{
if (write)
while (count--) NDS::ARM9Write8(address++, *buffer++);
else
while (count--) *buffer++ = SafeToPeek<true>(address) ? NDS::ARM9Read8(address) : 0, address++;
}
static void ARM7Access(u8* buffer, s64 address, s64 count, bool write)
{
if (write)
while (count--) NDS::ARM7Write8(address++, *buffer++);
else
while (count--) *buffer++ = SafeToPeek<false>(address) ? NDS::ARM7Read8(address) : 0, address++;
}
EXPORT void GetMemoryAreas(MemoryArea *m)
{
m[0].Data = NDS::ARM9BIOS;
m[0].Name = "ARM9 BIOS";
m[0].Size = sizeof NDS::ARM9BIOS;
m[0].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE;
m[1].Data = NDS::ARM7BIOS;
m[1].Name = "ARM7 BIOS";
m[1].Size = sizeof NDS::ARM7BIOS;
m[1].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE;
m[2].Data = NDS::MainRAM;
m[2].Name = "Main RAM";
m[2].Size = NDS::MainRAMMaxSize;
m[2].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY;
m[3].Data = NDS::SharedWRAM;
m[3].Name = "Shared WRAM";
m[3].Size = NDS::SharedWRAMSize;
m[3].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE;
m[4].Data = NDS::ARM7WRAM;
m[4].Name = "ARM7 WRAM";
m[4].Size = NDS::ARM7WRAMSize;
m[4].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE;
m[5].Data = (void*)ARM9Access;
m[5].Name = "ARM9 System Bus";
m[5].Size = 1ull << 32;
m[5].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_FUNCTIONHOOK;
m[6].Data = (void*)ARM7Access;
m[6].Name = "ARM7 System Bus";
m[6].Size = 1ull << 32;
m[6].Flags = MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_FUNCTIONHOOK;
// fixme: include more shit
}
struct MyFrameInfo : public FrameInfo
{
s64 Time;
u32 Keys;
s8 TouchX;
s8 TouchY;
s16 MicInput;
s8 GBALightSensor;
};
static s16 biz_mic_input[735];
static bool ValidRange(s8 sensor)
{
return (sensor >= 0) && (sensor <= 10);
}
static int sampPos = 0;
static void MicFeedNoise()
{
int sampLen = sizeof mic_blow / sizeof mic_blow[0];
for (int i = 0; i < 735; i++)
{
biz_mic_input[i] = mic_blow[sampPos++];
if (sampPos >= sampLen) sampPos = 0;
}
}
EXPORT void FrameAdvance(MyFrameInfo* f)
{
if (f->Keys & 0x8000)
{
NDS::LoadBIOS(false);
if (biz_skip_fw) NDS::SetupDirectBoot();
}
NDS::SetKeyMask(~f->Keys & 0xFFF);
if (f->Keys & 0x1000)
NDS::TouchScreen(f->TouchX, f->TouchY);
else
NDS::ReleaseScreen();
if (f->Keys & 0x2000)
NDS::SetLidClosed(false);
else if (f->Keys & 0x4000)
NDS::SetLidClosed(true);
if (f->MicInput < 0)
MicFeedNoise();
else
std::fill_n(biz_mic_input, 735, f->MicInput << 4);
NDS::MicInputFrame(biz_mic_input, 735);
int sensor = GBACart::SetInput(0, 1);
if (sensor != -1 && ValidRange(f->GBALightSensor))
{
if (sensor > f->GBALightSensor)
{
while (GBACart::SetInput(0, 1) != f->GBALightSensor) {}
}
else if (sensor < f->GBALightSensor)
{
while (GBACart::SetInput(1, 1) != f->GBALightSensor) {}
}
}
biz_time = f->Time;
NDS::RunFrame();
for (int i = 0; i < (256 * 192); i++)
{
f->VideoBuffer[i] = GPU::Framebuffer[GPU::FrontBuffer][0][i];
f->VideoBuffer[(256 * 192) + i] = GPU::Framebuffer[GPU::FrontBuffer][1][i];
}
f->Width = 256;
f->Height = 384;
f->Samples = SPU::GetOutputSize() / 2;
SPU::ReadOutput(f->SoundBuffer, f->Samples);
f->Cycles = NDS::GetSysClockCycles(2);
f->Lagged = NDS::LagFrameFlag;
}
void (*InputCallback)();
EXPORT void SetInputCallback(void (*callback)())
{
InputCallback = callback;
}
EXPORT void GetRegs(u32* regs)
{
NDS::GetRegs(regs);
}
EXPORT void SetReg(s32 ncpu, s32 index, s32 val)
{
NDS::SetReg(ncpu, index, val);
}
EXPORT void SetTraceCallback(void (*callback)(u32 cpu, u32* regs, u32 opcode, s64 ccoffset))
{
TraceCallback = callback;
}

View File

@ -0,0 +1,146 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <thread>
#include <mutex>
#include "Platform.h"
namespace Platform
{
void Init(int argc, char** argv)
{
}
void DeInit()
{
}
void StopEmu()
{
}
FILE* OpenFile(const char* path, const char* mode, bool mustexist)
{
return fopen(path, mode);
}
FILE* OpenLocalFile(const char* path, const char* mode)
{
return fopen(path, mode);
}
Thread* Thread_Create(std::function<void()> func)
{
std::thread* t = new std::thread(func);
return (Thread*) t;
}
void Thread_Free(Thread* thread)
{
delete (std::thread*) thread;
}
void Thread_Wait(Thread* thread)
{
((std::thread*) thread)->join();
}
Semaphore* Semaphore_Create()
{
sem_t* s = new sem_t;
sem_init(s, 0, 1);
return (Semaphore*) s;
}
void Semaphore_Free(Semaphore* sema)
{
sem_destroy((sem_t*) sema);
delete (sem_t*) sema;
}
void Semaphore_Reset(Semaphore* sema)
{
while (!sem_trywait((sem_t*) sema)) {};
}
void Semaphore_Wait(Semaphore* sema)
{
sem_wait((sem_t*) sema);
}
void Semaphore_Post(Semaphore* sema, int count)
{
while (count--) sem_post((sem_t*) sema);
}
Mutex* Mutex_Create()
{
std::mutex* m = new std::mutex();
return (Mutex*) m;
}
void Mutex_Free(Mutex* mutex)
{
delete (std::mutex*) mutex;
}
void Mutex_Lock(Mutex* mutex)
{
((std::mutex*) mutex)->lock();
}
void Mutex_Unlock(Mutex* mutex)
{
((std::mutex*) mutex)->unlock();
}
bool Mutex_TryLock(Mutex* mutex)
{
return ((std::mutex*) mutex)->try_lock();
}
bool MP_Init()
{
return false;
}
void MP_DeInit()
{
}
int MP_SendPacket(u8* data, int len)
{
return 0;
}
int MP_RecvPacket(u8* data, bool block)
{
return 0;
}
bool LAN_Init()
{
return false;
}
void LAN_DeInit()
{
}
int LAN_SendPacket(u8* data, int len)
{
return 0;
}
int LAN_RecvPacket(u8* data)
{
return 0;
}
void Sleep(u64 usecs)
{
}
}

78
waterbox/melon/Makefile Normal file
View File

@ -0,0 +1,78 @@
CXXFLAGS := -DMELONDS_VERSION="" \
-I./melonDS/src -I./melonDS/src/teakra/include \
-Wall -Wextra -Werror=int-to-pointer-cast \
-Wcast-qual -Wfatal-errors -Wno-missing-braces \
-Wno-unused-parameter -Wno-parentheses -Wno-sign-compare \
-Wno-unused-variable -Wno-unused-function \
-pedantic -std=c++17
TARGET = melonDS.wbx
CORE_SRCS = \
AREngine.cpp \
ARM.cpp \
ARMInterpreter.cpp \
ARMInterpreter_ALU.cpp \
ARMInterpreter_Branch.cpp \
ARMInterpreter_LoadStore.cpp \
Config.cpp \
CP15.cpp \
CRC32.cpp \
DMA.cpp \
DSi.cpp \
DSi_AES.cpp \
DSi_Camera.cpp \
DSi_DSP.cpp \
DSi_I2C.cpp \
DSi_NAND.cpp \
DSi_NDMA.cpp \
DSi_NWifi.cpp \
DSi_SD.cpp \
DSi_SPI_TSC.cpp \
GBACart.cpp \
GPU.cpp \
GPU2D.cpp \
GPU2D_Soft.cpp \
GPU3D.cpp \
GPU3D_Soft.cpp \
NDS.cpp \
NDSCart.cpp \
NDSCart_SRAMManager.cpp \
RTC.cpp \
Savestate.cpp \
SPI.cpp \
SPU.cpp \
Wifi.cpp \
WifiAP.cpp
TEAKRA_SRCS = \
ahbm.cpp \
apbp.cpp \
btdmp.cpp \
dma.cpp \
memory_interface.cpp \
mmio.cpp \
processor.cpp \
teakra.cpp \
timer.cpp
FATFS_SRCS = \
diskio.c \
ff.c \
ffsystem.c \
ffunicode.c
MISC_SRCS = \
sha1/sha1.c \
tiny-AES-c/aes.c \
xxhash/xxhash.c
SRCS = \
$(addprefix melonDS/src/,$(CORE_SRCS)) \
$(addprefix melonDS/src/teakra/src/,$(TEAKRA_SRCS)) \
$(addprefix melonDS/src/fatfs/,$(FATFS_SRCS)) \
$(addprefix melonDS/src/,$(MISC_SRCS)) \
BizInterface.cpp \
BizPlatform.cpp
include ../common.mak

@ -0,0 +1 @@
Subproject commit 7aa90df298c1d322852b1016937c2cb57aabe6ce

View File

@ -49,6 +49,7 @@ It consists of a modified musl libc, and build scripts to tie it all together.
7. You are now ready to start building cores. Each supports `make` and `make install`, as well as `make debug` and `make install-debug` for local development. From the root directory, the following should all be valid:
cd gpgx && make install
cd libsnes && make install
cd melon && make install
cd nyma && make -f faust.mak install
cd nyma && make -f ngp.mak install
cd nyma && make -f turbo.mak install

View File

@ -250,9 +250,8 @@ pub extern fn wbx_seal(obj: &mut WaterboxHost, ret: &mut Return<()>) {
}
/// Mounts a file in the environment. All data will be immediately consumed from the reader, which will not be used after this call.
/// To prevent nondeterminism, adding and removing files is very limited WRT savestates. If a file is writable, it must never exist
/// when save_state is called, and can only be used for transient operations. If a file is readable, it can appear in savestates,
/// but it must exist in every savestate and the exact sequence of add_file calls must be consistent from savestate to savestate.
/// To prevent nondeterminism, adding and removing files is very limited WRT savestates. Every file added must either exist
/// in every savestate, or never appear in any savestates. All savestateable files must be added in the same order for every run.
#[no_mangle]
pub extern fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callback: ReadCallback, userdata: usize, writable: bool, ret: &mut Return<()>) {
let mut reader = CReader {
@ -268,6 +267,7 @@ pub extern fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callba
/// Remove a file previously added. Writer is optional; if provided, the contents of the file at time of removal will be dumped to it.
/// It is an error to remove a file which is currently open in the guest.
/// If the file has been used in savestates, it does not make sense to remove it here, but nothing will stop you.
#[no_mangle]
pub extern fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, callback_opt: Option<WriteCallback>, userdata: usize, ret: &mut Return<()>) {
let res: anyhow::Result<()> = (|| {

View File

@ -127,9 +127,8 @@ impl FileSystem {
// pub fn set_missing_file_callback(&mut self, cb: Option<MissingFileCallback>) {
// self.missing_file_callback = cb;
// }
/// Accept a file from the outside world. Writable files may never appear in a savestate,
/// and readonly files must not be added or removed from savestate to savestate, so all uses
/// are either transient or read only resources that last for the life of emulation.
/// Accept a file from the outside world. Files must not be added or removed from savestate
/// to savestate, so all uses are either transient or resources that last for the life of emulation.
pub fn mount(&mut self, name: String, data: Vec<u8>, writable: bool) -> anyhow::Result<()> {
if self.files.iter().any(|f| f.name == name) {
return Err(anyhow!("File with name {} already mounted.", name))
@ -339,6 +338,54 @@ mod tests {
Ok(())
}
#[test]
fn test_rw_state() -> TestResult {
let mut fs = FileSystem::new();
fs.mount("myfile".to_string(),
"The quick brown fox jumps over the lazy dog.".to_string().into_bytes(), true)?;
let mut buff = vec![0u8; 8];
{
let fdr = fs.open("myfile", O_RDONLY, 0)?;
assert_eq!(fdr.0, 3);
// TODO: If a file is mounted as writable, opening it as readonly still allows it to be written to.
// assert!(fs.write(fdr, &buff[..]).is_err());
assert_eq!(fs.read(fdr, &mut buff[..])?, 8);
assert_eq!(buff, "The quic".as_bytes());
fs.close(fdr)?;
}
let fd = fs.open("myfile", O_RDWR, 0)?;
assert_eq!(fd.0, 3);
let mut state0 = Vec::new();
fs.save_state(&mut state0)?;
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "The quic".as_bytes());
fs.write(fd, &buff[..])?;
fs.seek(fd, 0, SEEK_SET)?;
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "The quic".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "The quic".as_bytes());
fs.load_state(&mut &state0[..])?;
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "The quic".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "k brown ".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "fox jump".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "s over t".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 8);
assert_eq!(buff, "he lazy ".as_bytes());
assert_eq!(fs.read(fd, &mut buff[..])?, 4);
assert_eq!(&buff[0..4], "dog.".as_bytes());
Ok(())
}
#[test]
fn test_negative() -> TestResult {
let mut fs = FileSystem::new();
@ -347,7 +394,6 @@ mod tests {
assert!(fs.unmount("/dev/stdout").is_err()); // unmounting permanent file
fs.mount("oopopo".to_string(), Vec::new(), true)?;
let mut state0 = Vec::new();
assert!(fs.save_state(&mut state0).is_err()); // save state with transient file
state0.resize(0, 0);
fs.unmount("oopopo")?;
fs.mount("oopopo".to_string(), Vec::new(), false)?;

View File

@ -30,10 +30,15 @@ impl IStateable for RegularFile {
bin::write_magic(stream, "RegularFile")?;
bin::write_hash(stream, &hash[..])?;
bin::write(stream, &self.position)?;
Ok(())
Ok(())
},
None => {
bin::write_magic(stream, "RegularFile(RW)")?;
bin::writeval(stream, self.data.len())?;
stream.write_all(&self.data[..])?;
bin::write(stream, &self.position)?;
Ok(())
},
None => Err(anyhow!("Cannot save state while transient files are active"))
}
}
fn load_state(&mut self, stream: &mut dyn Read) -> anyhow::Result<()> {
@ -42,9 +47,18 @@ impl IStateable for RegularFile {
bin::verify_magic(stream, "RegularFile")?;
bin::verify_hash(stream, &hash[..])?;
bin::read(stream, &mut self.position)?;
Ok(())
Ok(())
}
None => Err(anyhow!("Cannot load state while transient files are active"))
None => {
bin::verify_magic(stream, "RegularFile(RW)")?;
let len = bin::readval(stream)?;
if len != self.data.len() {
self.data.resize(len, 0);
}
stream.read_exact(&mut self.data[..])?;
bin::read(stream, &mut self.position)?;
Ok(())
},
}
}
}

View File

@ -416,7 +416,7 @@ impl MemoryBlock {
struct Chunk {
addr: AddressRange,
prot: Protection,
};
}
let mut start = range.start;
let chunks = range.iter()
.map(|p| {