RetroAchievements Support (#3407)
This commit is contained in:
parent
eb1cef1ffc
commit
92c1cdff22
|
@ -55,3 +55,6 @@
|
||||||
path = waterbox/mame-arcade/mame
|
path = waterbox/mame-arcade/mame
|
||||||
url = https://github.com/TASEmulators/mame.git
|
url = https://github.com/TASEmulators/mame.git
|
||||||
branch = mamehawk0250
|
branch = mamehawk0250
|
||||||
|
[submodule "ExternalProjects/librcheevos/rcheevos"]
|
||||||
|
path = ExternalProjects/librcheevos/rcheevos
|
||||||
|
url = https://github.com/RetroAchievements/rcheevos.git
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 277 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"Popup": {
|
||||||
|
"Font": "Tahoma",
|
||||||
|
"FontSizes": {
|
||||||
|
"Title": 26,
|
||||||
|
"Subtitle": 18,
|
||||||
|
"Detail": 16,
|
||||||
|
"LeaderboardTitle": 22,
|
||||||
|
"LeaderboardEntry": 18,
|
||||||
|
"LeaderboardTracker": 18
|
||||||
|
},
|
||||||
|
"Colors": {
|
||||||
|
"Background": "#FB6600",
|
||||||
|
"NonHardcoreBackground": "#FB6600",
|
||||||
|
"MasteryBackground": "#FB6600",
|
||||||
|
"Border": "#A04020",
|
||||||
|
"TextShadow": "#B45000",
|
||||||
|
"Title": "#000000",
|
||||||
|
"Description": "#000000",
|
||||||
|
"Detail": "#000000",
|
||||||
|
"Error": "#E0B080",
|
||||||
|
"LeaderboardEntry": "#000000",
|
||||||
|
"LeaderboardPlayer": "#E0B080"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Overlay": {
|
||||||
|
"Font": "Tahoma",
|
||||||
|
"FontSizes": {
|
||||||
|
"Title": 32,
|
||||||
|
"Header": 26,
|
||||||
|
"Summary": 22
|
||||||
|
},
|
||||||
|
"Colors": {
|
||||||
|
"Panel": "#202020",
|
||||||
|
"Text": "#1166DD",
|
||||||
|
"DisabledText": "#747A90",
|
||||||
|
"SubText": "#8C8C8C",
|
||||||
|
"DisabledSubText": "#606060",
|
||||||
|
"SelectionBackground": "#16163C",
|
||||||
|
"SelectionText": "#FFFFFF",
|
||||||
|
"SelectionDisabledText": "#C8C8C8",
|
||||||
|
"ScrollBar": "#404040",
|
||||||
|
"ScrollBarGripper": "#C0C0C0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Transparent": true
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"Popup": {
|
||||||
|
"Font": "Tahoma",
|
||||||
|
"FontSizes": {
|
||||||
|
"Title": 26,
|
||||||
|
"Subtitle": 18,
|
||||||
|
"Detail": 16,
|
||||||
|
"LeaderboardTitle": 22,
|
||||||
|
"LeaderboardEntry": 18,
|
||||||
|
"LeaderboardTracker": 18
|
||||||
|
},
|
||||||
|
"Colors": {
|
||||||
|
"Background": "#404040",
|
||||||
|
"NonHardcoreBackground": "#405068",
|
||||||
|
"MasteryBackground": "#685820",
|
||||||
|
"Border": "#202020",
|
||||||
|
"TextShadow": "#202020",
|
||||||
|
"Title": "#FFFFFF",
|
||||||
|
"Description": "#F0E040",
|
||||||
|
"Detail": "#40C0E0",
|
||||||
|
"Error": "#E02020",
|
||||||
|
"LeaderboardEntry": "#B4B4B4",
|
||||||
|
"LeaderboardPlayer": "#F0E080"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Overlay": {
|
||||||
|
"Font": "Tahoma",
|
||||||
|
"FontSizes": {
|
||||||
|
"Title": 32,
|
||||||
|
"Header": 26,
|
||||||
|
"Summary": 22
|
||||||
|
},
|
||||||
|
"Colors": {
|
||||||
|
"Panel": "#202020",
|
||||||
|
"Text": "#1166DD",
|
||||||
|
"DisabledText": "#747A90",
|
||||||
|
"SubText": "#8C8C8C",
|
||||||
|
"DisabledSubText": "#606060",
|
||||||
|
"SelectionBackground": "#16163C",
|
||||||
|
"SelectionText": "#FFFFFF",
|
||||||
|
"SelectionDisabledText": "#C8C8C8",
|
||||||
|
"ScrollBar": "#404040",
|
||||||
|
"ScrollBarGripper": "#C0C0C0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Transparent": true
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,79 @@
|
||||||
|
ROOT_DIR := $(realpath .)
|
||||||
|
OUTPUTDLL_DIR := $(realpath $(ROOT_DIR)/../../Assets/dll)
|
||||||
|
OUTPUTDLLCOPY_DIR := $(realpath $(ROOT_DIR)/../../output/dll)
|
||||||
|
|
||||||
|
OUT_DIR := $(ROOT_DIR)/obj
|
||||||
|
OBJ_DIR := $(OUT_DIR)/release
|
||||||
|
DOBJ_DIR := $(OUT_DIR)/debug
|
||||||
|
|
||||||
|
CC := gcc
|
||||||
|
CCFLAGS := -I$(ROOT_DIR)/rcheevos/include -Wall -Wextra -std=c89 \
|
||||||
|
-Wno-implicit-fallthrough -Wno-missing-field-initializers \
|
||||||
|
-Wno-unused-parameter -Wno-maybe-uninitialized -DRC_DISABLE_LUA
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
TARGET := librcheevos.dll
|
||||||
|
else
|
||||||
|
TARGET := librcheevos.so
|
||||||
|
endif
|
||||||
|
|
||||||
|
# rc_libretro.c wants libretro.h which is expected by the builder to provide (and that file is huge)
|
||||||
|
# not needed for our purposes anyways, so don't bother
|
||||||
|
SRCS := $(filter-out %rc_libretro.c,$(shell find $(ROOT_DIR)/rcheevos/src -type f -name '*.c'))
|
||||||
|
|
||||||
|
LDFLAGS := -shared
|
||||||
|
CCFLAGS_DEBUG := -O0 -g
|
||||||
|
CCFLAGS_RELEASE := -O3 -flto
|
||||||
|
LDFLAGS_DEBUG :=
|
||||||
|
LDFLAGS_RELEASE := -s
|
||||||
|
|
||||||
|
_OBJS := $(addsuffix .o,$(realpath $(SRCS)))
|
||||||
|
OBJS := $(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS))
|
||||||
|
DOBJS := $(patsubst $(ROOT_DIR)%,$(DOBJ_DIR)%,$(_OBJS))
|
||||||
|
|
||||||
|
$(OBJ_DIR)/%.c.o: %.c
|
||||||
|
@echo cc $<
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@$(CC) -c -o $@ $< $(CCFLAGS) $(CCFLAGS_RELEASE)
|
||||||
|
$(DOBJ_DIR)/%.c.o: %.c
|
||||||
|
@echo cc $<
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@$(CC) -c -o $@ $< $(CCFLAGS) $(CCFLAGS_DEBUG)
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := install
|
||||||
|
|
||||||
|
TARGET_RELEASE := $(OBJ_DIR)/$(TARGET)
|
||||||
|
TARGET_DEBUG := $(DOBJ_DIR)/$(TARGET)
|
||||||
|
|
||||||
|
.PHONY: release debug install install-debug
|
||||||
|
|
||||||
|
release: $(TARGET_RELEASE)
|
||||||
|
debug: $(TARGET_DEBUG)
|
||||||
|
|
||||||
|
$(TARGET_RELEASE): $(OBJS)
|
||||||
|
@echo ld $@
|
||||||
|
@$(CC) -o $@ $(LDFLAGS) $(LDFLAGS_RELEASE) $(CCFLAGS) $(CCFLAGS_RELEASE) $(OBJS)
|
||||||
|
$(TARGET_DEBUG): $(DOBJS)
|
||||||
|
@echo ld $@
|
||||||
|
@$(CC) -o $@ $(LDFLAGS) $(LDFLAGS_DEBUG) $(CCFLAGS) $(CCFLAGS_DEBUG) $(DOBJS)
|
||||||
|
|
||||||
|
install: $(TARGET_RELEASE)
|
||||||
|
@cp -f $< $(OUTPUTDLL_DIR)
|
||||||
|
@cp $(OUTPUTDLL_DIR)/$(TARGET) $(OUTPUTDLLCOPY_DIR)/$(TARGET) || true
|
||||||
|
@echo Release build of $(TARGET) installed.
|
||||||
|
|
||||||
|
install-debug: $(TARGET_DEBUG)
|
||||||
|
@cp -f $< $(OUTPUTDLL_DIR)
|
||||||
|
@cp $(OUTPUTDLL_DIR)/$(TARGET) $(OUTPUTDLLCOPY_DIR)/$(TARGET) || true
|
||||||
|
@echo Debug build of $(TARGET) installed.
|
||||||
|
|
||||||
|
.PHONY: clean clean-release clean-debug
|
||||||
|
clean:
|
||||||
|
rm -rf $(OUT_DIR)
|
||||||
|
clean-release:
|
||||||
|
rm -rf $(OUT_DIR)/release
|
||||||
|
clean-debug:
|
||||||
|
rm -rf $(OUT_DIR)/debug
|
||||||
|
|
||||||
|
-include $(OBJS:%o=%d)
|
||||||
|
-include $(DOBJS:%o=%d)
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 165d1a3b189c472c4547ac385e59be2f527e688e
|
|
@ -84,6 +84,10 @@ namespace BizHawk.BizInvoke
|
||||||
{
|
{
|
||||||
return new CustomAttributeBuilder(t.GetConstructor(Type.EmptyTypes)!, Array.Empty<object>());
|
return new CustomAttributeBuilder(t.GetConstructor(Type.EmptyTypes)!, Array.Empty<object>());
|
||||||
}
|
}
|
||||||
|
else if (t == typeof(MarshalAsAttribute))
|
||||||
|
{
|
||||||
|
return new CustomAttributeBuilder(t.GetConstructor(new[] { typeof(UnmanagedType) })!, new object[] { ((MarshalAsAttribute)o).Value });
|
||||||
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException($"Unknown parameter attribute {t.Name}");
|
throw new InvalidOperationException($"Unknown parameter attribute {t.Name}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,12 +388,20 @@ namespace BizHawk.BizInvoke
|
||||||
pli.EmitLoad();
|
pli.EmitLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WantsWinAPIBool()
|
||||||
|
{
|
||||||
|
var attrs = baseMethod.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(MarshalAsAttribute), false);
|
||||||
|
return attrs.Length > 0 && ((MarshalAsAttribute)attrs[0]).Value is UnmanagedType.Bool;
|
||||||
|
}
|
||||||
|
|
||||||
il.Emit(OpCodes.Ldarg_0);
|
il.Emit(OpCodes.Ldarg_0);
|
||||||
il.Emit(OpCodes.Ldfld, field);
|
il.Emit(OpCodes.Ldfld, field);
|
||||||
il.EmitCalli(
|
il.EmitCalli(
|
||||||
OpCodes.Calli,
|
OpCodes.Calli,
|
||||||
nativeCall,
|
nativeCall,
|
||||||
returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage
|
returnType != typeof(bool) || WantsWinAPIBool()
|
||||||
|
? returnType
|
||||||
|
: typeof(byte), // undo winapi style bool garbage by default
|
||||||
paramLoadInfos.Select(p => p.NativeType).ToArray());
|
paramLoadInfos.Select(p => p.NativeType).ToArray());
|
||||||
|
|
||||||
if (monitorField != null) // monitor: finally exit
|
if (monitorField != null) // monitor: finally exit
|
||||||
|
|
|
@ -177,6 +177,15 @@ namespace BizHawk.Client.Common
|
||||||
Bind("NDS", "Previous Screen Layout");
|
Bind("NDS", "Previous Screen Layout");
|
||||||
Bind("NDS", "Screen Rotate");
|
Bind("NDS", "Screen Rotate");
|
||||||
|
|
||||||
|
Bind("RAIntegration", "Open RA Overlay", "Escape");
|
||||||
|
Bind("RAIntegration", "RA Up", "Up");
|
||||||
|
Bind("RAIntegration", "RA Down", "Down");
|
||||||
|
Bind("RAIntegration", "RA Left", "Left");
|
||||||
|
Bind("RAIntegration", "RA Right", "Right");
|
||||||
|
Bind("RAIntegration", "RA Confirm", "X");
|
||||||
|
Bind("RAIntegration", "RA Cancel", "Z");
|
||||||
|
Bind("RAIntegration", "RA Quit", "Backspace");
|
||||||
|
|
||||||
AllHotkeys = dict;
|
AllHotkeys = dict;
|
||||||
Groupings = dict.Values.Select(static info => info.TabGroup).Distinct().ToList();
|
Groupings = dict.Values.Select(static info => info.TabGroup).Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,7 +358,19 @@ namespace BizHawk.Client.Common
|
||||||
public int OSDMessageDuration { get; set; } = 2;
|
public int OSDMessageDuration { get; set; } = 2;
|
||||||
|
|
||||||
public Queue<string> RecentCores { get; set; } = new();
|
public Queue<string> RecentCores { get; set; } = new();
|
||||||
|
|
||||||
public Dictionary<string, string> TrustedExtTools { get; set; } = new();
|
public Dictionary<string, string> TrustedExtTools { get; set; } = new();
|
||||||
|
|
||||||
|
// RetroAchievements settings
|
||||||
|
public bool SkipRATelemetryWarning { get; set; }
|
||||||
|
public string RAUsername { get; set; } = "";
|
||||||
|
public string RAToken { get; set; } = "";
|
||||||
|
public bool RACheevosActive { get; set; } = true;
|
||||||
|
public bool RALBoardsActive { get; set; }
|
||||||
|
public bool RARichPresenceActive { get; set; } = true;
|
||||||
|
public bool RAHardcoreMode { get; set; }
|
||||||
|
public bool RASoundEffects { get; set; } = true;
|
||||||
|
public bool RAAllowUnofficialCheevos { get; set; }
|
||||||
|
public bool RAAutostart { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,11 @@
|
||||||
<!-- creates circular dependency
|
<!-- creates circular dependency
|
||||||
<Compile Update="Properties/Resources.cs" DependentUpon="Resources.resx" />
|
<Compile Update="Properties/Resources.cs" DependentUpon="Resources.resx" />
|
||||||
-->
|
-->
|
||||||
|
<Compile Update="RetroAchievements/RAIntegrationDownloaderForm.cs" SubType="Form" />
|
||||||
|
<Compile Update="RetroAchievements/RAIntegrationDownloaderForm.Designer.cs" DependentUpon="RAIntegrationDownloaderForm.cs" />
|
||||||
|
<EmbeddedResource Update="RetroAchievements/RAIntegrationDownloaderForm.resx" DependentUpon="RAIntegrationDownloaderForm.cs" />
|
||||||
|
<Compile Update="RetroAchievements/RCheevos.*.cs" DependentUpon="RCheevos.cs" />
|
||||||
|
<Compile Update="RetroAchievements/RetroAchievements.*.cs" DependentUpon="RetroAchievements.cs" />
|
||||||
<Compile Update="RomStatusPicker.cs" SubType="Form" />
|
<Compile Update="RomStatusPicker.cs" SubType="Form" />
|
||||||
<Compile Update="RomStatusPicker.Designer.cs" DependentUpon="RomStatusPicker.cs" />
|
<Compile Update="RomStatusPicker.Designer.cs" DependentUpon="RomStatusPicker.cs" />
|
||||||
<EmbeddedResource Update="RomStatusPicker.resx" DependentUpon="RomStatusPicker.cs" />
|
<EmbeddedResource Update="RomStatusPicker.resx" DependentUpon="RomStatusPicker.cs" />
|
||||||
|
|
|
@ -214,6 +214,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.ExternalToolMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.ExternalToolMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
this.dummyExternalTool = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.dummyExternalTool = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
this.BatchRunnerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.BatchRunnerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
|
this.RetroAchievementsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
|
this.StartRetroAchievementsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
this.NESSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.NESSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
this.NESPPUViewerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.NESPPUViewerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
this.NESNametableViewerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
this.NESNametableViewerMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx();
|
||||||
|
@ -1368,6 +1370,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.MacroToolMenuItem,
|
this.MacroToolMenuItem,
|
||||||
this.VirtualPadMenuItem,
|
this.VirtualPadMenuItem,
|
||||||
this.BasicBotMenuItem,
|
this.BasicBotMenuItem,
|
||||||
|
this.RetroAchievementsMenuItem,
|
||||||
this.toolStripSeparator11,
|
this.toolStripSeparator11,
|
||||||
this.CheatsMenuItem,
|
this.CheatsMenuItem,
|
||||||
this.GameSharkConverterMenuItem,
|
this.GameSharkConverterMenuItem,
|
||||||
|
@ -1438,6 +1441,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.BasicBotMenuItem.Text = "Basic Bot";
|
this.BasicBotMenuItem.Text = "Basic Bot";
|
||||||
this.BasicBotMenuItem.Click += new System.EventHandler(this.BasicBotMenuItem_Click);
|
this.BasicBotMenuItem.Click += new System.EventHandler(this.BasicBotMenuItem_Click);
|
||||||
//
|
//
|
||||||
|
// RetroAchievementsMenuItem
|
||||||
|
//
|
||||||
|
this.RetroAchievementsMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||||
|
StartRetroAchievementsMenuItem});
|
||||||
|
this.RetroAchievementsMenuItem.Text = "&RetroAchievements";
|
||||||
|
//
|
||||||
|
// StartRetroAchievementsMenuItem
|
||||||
|
//
|
||||||
|
this.StartRetroAchievementsMenuItem.Text = "&Start RetroAchievements";
|
||||||
|
this.StartRetroAchievementsMenuItem.Click += new System.EventHandler(this.StartRetroAchievementsMenuItem_Click);
|
||||||
|
//
|
||||||
// CheatsMenuItem
|
// CheatsMenuItem
|
||||||
//
|
//
|
||||||
this.CheatsMenuItem.Text = "Cheats";
|
this.CheatsMenuItem.Text = "Cheats";
|
||||||
|
@ -2459,6 +2473,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ViewSubMenu;
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ViewSubMenu;
|
||||||
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ConfigSubMenu;
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ConfigSubMenu;
|
||||||
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ToolsSubMenu;
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx ToolsSubMenu;
|
||||||
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx RetroAchievementsMenuItem;
|
||||||
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx StartRetroAchievementsMenuItem;
|
||||||
private BizHawk.WinForms.Controls.ToolStripMenuItemEx HelpSubMenu;
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx HelpSubMenu;
|
||||||
private BizHawk.WinForms.Controls.ToolStripMenuItemEx PauseMenuItem;
|
private BizHawk.WinForms.Controls.ToolStripMenuItemEx PauseMenuItem;
|
||||||
private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator1;
|
private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator1;
|
||||||
|
|
|
@ -1314,6 +1314,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
form.ShowDialog();
|
form.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartRetroAchievementsMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OpenRetroAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
private void NesSubMenu_DropDownOpened(object sender, EventArgs e)
|
private void NesSubMenu_DropDownOpened(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var boardName = Emulator.HasBoardInfo() ? Emulator.AsBoardInfo().BoardName : null;
|
var boardName = Emulator.HasBoardInfo() ? Emulator.AsBoardInfo().BoardName : null;
|
||||||
|
|
|
@ -589,6 +589,14 @@ namespace BizHawk.Client.EmuHawk
|
||||||
case "Turbo":
|
case "Turbo":
|
||||||
case "Rewind":
|
case "Rewind":
|
||||||
case "Fast Forward":
|
case "Fast Forward":
|
||||||
|
case "Open RA Overlay":
|
||||||
|
case "RA Up":
|
||||||
|
case "RA Down":
|
||||||
|
case "RA Left":
|
||||||
|
case "RA Right":
|
||||||
|
case "RA Confirm":
|
||||||
|
case "RA Cancel":
|
||||||
|
case "RA Quit":
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ using BizHawk.WinForms.Controls;
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
public partial class MainForm : FormBase, IDialogParent, IMainFormForApi, IMainFormForTools
|
public partial class MainForm : FormBase, IDialogParent, IMainFormForApi, IMainFormForTools, IMainFormForRetroAchievements
|
||||||
{
|
{
|
||||||
private static readonly FilesystemFilterSet EmuHawkSaveStatesFSFilterSet = new(FilesystemFilter.EmuHawkSaveStates);
|
private static readonly FilesystemFilterSet EmuHawkSaveStatesFSFilterSet = new(FilesystemFilter.EmuHawkSaveStates);
|
||||||
|
|
||||||
|
@ -698,6 +698,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
SynchChrome();
|
SynchChrome();
|
||||||
|
|
||||||
|
if (Config.RAAutostart)
|
||||||
|
{
|
||||||
|
OpenRetroAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
_presentationPanel.Control.Paint += (o, e) =>
|
_presentationPanel.Control.Paint += (o, e) =>
|
||||||
{
|
{
|
||||||
// I would like to trigger a repaint here, but this isn't done yet
|
// I would like to trigger a repaint here, but this isn't done yet
|
||||||
|
@ -838,6 +843,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
DisplayManager = null;
|
DisplayManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RA?.Dispose();
|
||||||
|
RA = null;
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
components?.Dispose();
|
components?.Dispose();
|
||||||
|
@ -2970,6 +2978,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
ExtToolManager.Restart(Config);
|
ExtToolManager.Restart(Config);
|
||||||
Sound.Config = Config;
|
Sound.Config = Config;
|
||||||
DisplayManager.UpdateGlobals(Config, Emulator);
|
DisplayManager.UpdateGlobals(Config, Emulator);
|
||||||
|
RA?.Restart();
|
||||||
AddOnScreenMessage($"Config file loaded: {iniPath}");
|
AddOnScreenMessage($"Config file loaded: {iniPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3010,6 +3019,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
runFrame = true;
|
runFrame = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RA?.Update();
|
||||||
|
|
||||||
bool oldFrameAdvanceCondition = InputManager.ClientControls["Frame Advance"] || PressFrameAdvance || HoldFrameAdvance;
|
bool oldFrameAdvanceCondition = InputManager.ClientControls["Frame Advance"] || PressFrameAdvance || HoldFrameAdvance;
|
||||||
if (FrameInch)
|
if (FrameInch)
|
||||||
|
@ -3132,6 +3142,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
MovieSession.HandleFrameBefore();
|
MovieSession.HandleFrameBefore();
|
||||||
|
|
||||||
|
RA?.OnFrameAdvance();
|
||||||
|
|
||||||
if (Config.AutosaveSaveRAM)
|
if (Config.AutosaveSaveRAM)
|
||||||
{
|
{
|
||||||
if (AutoFlushSaveRamIn-- <= 0)
|
if (AutoFlushSaveRamIn-- <= 0)
|
||||||
|
@ -4006,6 +4018,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
UpdateCoreStatusBarButton();
|
UpdateCoreStatusBarButton();
|
||||||
UpdateDumpInfo();
|
UpdateDumpInfo();
|
||||||
SetMainformMovieInfo();
|
SetMainformMovieInfo();
|
||||||
|
RA?.Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CommitCoreSettingsToConfig()
|
private void CommitCoreSettingsToConfig()
|
||||||
|
@ -4071,6 +4084,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
StopMovie();
|
StopMovie();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RA?.Stop();
|
||||||
|
|
||||||
CheatList.SaveOnClose();
|
CheatList.SaveOnClose();
|
||||||
Emulator.Dispose();
|
Emulator.Dispose();
|
||||||
Emulator = new NullEmulator();
|
Emulator = new NullEmulator();
|
||||||
|
@ -4210,6 +4225,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
OSD.ClearGuiText();
|
OSD.ClearGuiText();
|
||||||
EmuClient.OnStateLoaded(this, userFriendlyStateName);
|
EmuClient.OnStateLoaded(this, userFriendlyStateName);
|
||||||
|
RA?.OnLoadState(path);
|
||||||
|
|
||||||
if (Tools.Has<LuaConsole>())
|
if (Tools.Has<LuaConsole>())
|
||||||
{
|
{
|
||||||
|
@ -4288,6 +4304,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
new SavestateFile(Emulator, MovieSession, QuickBmpFile, MovieSession.UserBag).Create(path, Config.Savestates);
|
new SavestateFile(Emulator, MovieSession, QuickBmpFile, MovieSession.UserBag).Create(path, Config.Savestates);
|
||||||
|
|
||||||
EmuClient.OnStateSaved(this, userFriendlyStateName);
|
EmuClient.OnStateSaved(this, userFriendlyStateName);
|
||||||
|
RA?.OnSaveState(path);
|
||||||
|
|
||||||
if (!suppressOSD)
|
if (!suppressOSD)
|
||||||
{
|
{
|
||||||
|
@ -4885,5 +4902,20 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQuickBmpFile QuickBmpFile { get; } = EmuHawk.QuickBmpFile.INSTANCE;
|
public IQuickBmpFile QuickBmpFile { get; } = EmuHawk.QuickBmpFile.INSTANCE;
|
||||||
|
|
||||||
|
private IRetroAchievements RA { get; set; }
|
||||||
|
|
||||||
|
private void OpenRetroAchievements()
|
||||||
|
{
|
||||||
|
RA = RetroAchievements.CreateImpl(this, InputManager, Tools, () => Config, RetroAchievementsMenuItem.DropDownItems, () =>
|
||||||
|
{
|
||||||
|
RA.Dispose();
|
||||||
|
RA = null;
|
||||||
|
RetroAchievementsMenuItem.DropDownItems.Clear();
|
||||||
|
RetroAchievementsMenuItem.DropDownItems.Add(StartRetroAchievementsMenuItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
RA?.Restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public interface IMainFormForRetroAchievements : IDialogController
|
||||||
|
{
|
||||||
|
LoadRomArgs CurrentlyOpenRomArgs { get; }
|
||||||
|
|
||||||
|
EmuClientApi EmuClient { get; }
|
||||||
|
|
||||||
|
IEmulator Emulator { get; }
|
||||||
|
|
||||||
|
bool FrameInch { get; set; }
|
||||||
|
|
||||||
|
bool FastForward { get; set; }
|
||||||
|
|
||||||
|
GameInfo Game { get; }
|
||||||
|
|
||||||
|
IntPtr Handle { get; }
|
||||||
|
|
||||||
|
IMovieSession MovieSession { get; }
|
||||||
|
|
||||||
|
SettingsAdapter GetSettingsAdapterForLoadedCoreUntyped();
|
||||||
|
|
||||||
|
bool LoadRom(string path, LoadRomArgs args);
|
||||||
|
|
||||||
|
void PauseEmulator();
|
||||||
|
|
||||||
|
bool RebootCore();
|
||||||
|
|
||||||
|
void UpdateWindowTitle();
|
||||||
|
|
||||||
|
void UnpauseEmulator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public interface IRetroAchievements : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// General update, not tied to frame advance
|
||||||
|
/// Use this for overlay / checking hardcore mode
|
||||||
|
/// </summary>
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called on frame advance, process achievements here
|
||||||
|
/// </summary>
|
||||||
|
void OnFrameAdvance();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call this whenever the ROM changes (and after ctor)
|
||||||
|
/// Make sure CurrentlyOpenRomArgs is updated before calling this!
|
||||||
|
/// </summary>
|
||||||
|
void Restart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call this before the emulator is disposed
|
||||||
|
/// </summary>
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call this when a state is saved
|
||||||
|
/// In memory states (like with rewind) do not need to call this
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">path to .State file</param>
|
||||||
|
void OnSaveState(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call this when a state is loaded
|
||||||
|
/// In memory states (like with rewind) do not need to call this
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">path to .State file</param>
|
||||||
|
void OnLoadState(string path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,715 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using BizHawk.BizInvoke;
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // Naming Styles
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract class LibRCheevos
|
||||||
|
{
|
||||||
|
private const CallingConvention cc = CallingConvention.Cdecl;
|
||||||
|
|
||||||
|
public enum rc_error_t : int
|
||||||
|
{
|
||||||
|
RC_OK = 0,
|
||||||
|
RC_INVALID_LUA_OPERAND = -1,
|
||||||
|
RC_INVALID_MEMORY_OPERAND = -2,
|
||||||
|
RC_INVALID_CONST_OPERAND = -3,
|
||||||
|
RC_INVALID_FP_OPERAND = -4,
|
||||||
|
RC_INVALID_CONDITION_TYPE = -5,
|
||||||
|
RC_INVALID_OPERATOR = -6,
|
||||||
|
RC_INVALID_REQUIRED_HITS = -7,
|
||||||
|
RC_DUPLICATED_START = -8,
|
||||||
|
RC_DUPLICATED_CANCEL = -9,
|
||||||
|
RC_DUPLICATED_SUBMIT = -10,
|
||||||
|
RC_DUPLICATED_VALUE = -11,
|
||||||
|
RC_DUPLICATED_PROGRESS = -12,
|
||||||
|
RC_MISSING_START = -13,
|
||||||
|
RC_MISSING_CANCEL = -14,
|
||||||
|
RC_MISSING_SUBMIT = -15,
|
||||||
|
RC_MISSING_VALUE = -16,
|
||||||
|
RC_INVALID_LBOARD_FIELD = -17,
|
||||||
|
RC_MISSING_DISPLAY_STRING = -18,
|
||||||
|
RC_OUT_OF_MEMORY = -19,
|
||||||
|
RC_INVALID_VALUE_FLAG = -20,
|
||||||
|
RC_MISSING_VALUE_MEASURED = -21,
|
||||||
|
RC_MULTIPLE_MEASURED = -22,
|
||||||
|
RC_INVALID_MEASURED_TARGET = -23,
|
||||||
|
RC_INVALID_COMPARISON = -24,
|
||||||
|
RC_INVALID_STATE = -25,
|
||||||
|
RC_INVALID_JSON = -26
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum rc_runtime_event_type_t : byte
|
||||||
|
{
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_RESET,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED,
|
||||||
|
RC_RUNTIME_EVENT_LBOARD_STARTED,
|
||||||
|
RC_RUNTIME_EVENT_LBOARD_CANCELED,
|
||||||
|
RC_RUNTIME_EVENT_LBOARD_UPDATED,
|
||||||
|
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
|
||||||
|
RC_RUNTIME_EVENT_LBOARD_DISABLED,
|
||||||
|
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum rc_api_image_type_t : int
|
||||||
|
{
|
||||||
|
RC_IMAGE_TYPE_GAME = 1,
|
||||||
|
RC_IMAGE_TYPE_ACHIEVEMENT,
|
||||||
|
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED,
|
||||||
|
RC_IMAGE_TYPE_USER,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum rc_runtime_achievement_category_t : int
|
||||||
|
{
|
||||||
|
RC_ACHIEVEMENT_CATEGORY_CORE = 3,
|
||||||
|
RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_runtime_t
|
||||||
|
{
|
||||||
|
public IntPtr triggers;
|
||||||
|
public int trigger_count;
|
||||||
|
public int trigger_capacity;
|
||||||
|
public IntPtr lboards;
|
||||||
|
public int lboard_count;
|
||||||
|
public int lboard_capacity;
|
||||||
|
public IntPtr richpresence;
|
||||||
|
public IntPtr memrefs;
|
||||||
|
public IntPtr next_memref;
|
||||||
|
public IntPtr variables;
|
||||||
|
public IntPtr next_variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_runtime_event_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int value;
|
||||||
|
public rc_runtime_event_type_t type;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public unsafe struct rc_api_buffer_t
|
||||||
|
{
|
||||||
|
public IntPtr write;
|
||||||
|
public IntPtr end;
|
||||||
|
public IntPtr next;
|
||||||
|
public fixed byte data[256];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_request_t
|
||||||
|
{
|
||||||
|
public IntPtr url;
|
||||||
|
public IntPtr post_data;
|
||||||
|
public rc_api_buffer_t buffer;
|
||||||
|
|
||||||
|
public string URL => Mershul.PtrToStringUtf8(url);
|
||||||
|
public string PostData => Mershul.PtrToStringUtf8(post_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_response_t
|
||||||
|
{
|
||||||
|
public int succeeded;
|
||||||
|
public IntPtr error_message;
|
||||||
|
public rc_api_buffer_t buffer;
|
||||||
|
|
||||||
|
public string ErrorMessage => Mershul.PtrToStringUtf8(error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_user_unlocks_response_t
|
||||||
|
{
|
||||||
|
public IntPtr achievement_ids;
|
||||||
|
public int num_achievement_ids;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_login_response_t
|
||||||
|
{
|
||||||
|
public IntPtr username;
|
||||||
|
public IntPtr api_token;
|
||||||
|
public int score;
|
||||||
|
public int num_unread_messages;
|
||||||
|
public IntPtr display_name;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
|
||||||
|
public string Username => Mershul.PtrToStringUtf8(username);
|
||||||
|
public string ApiToken => Mershul.PtrToStringUtf8(api_token);
|
||||||
|
public string DisplayName => Mershul.PtrToStringUtf8(display_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_start_session_response_t
|
||||||
|
{
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_user_unlocks_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int game_id;
|
||||||
|
public int hardcore;
|
||||||
|
|
||||||
|
public rc_api_fetch_user_unlocks_request_t(string username, string api_token, int game_id, bool hardcore)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.game_id = game_id;
|
||||||
|
this.hardcore = hardcore ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_login_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public string password;
|
||||||
|
|
||||||
|
public rc_api_login_request_t(string username, string api_token, string password)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_start_session_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int game_id;
|
||||||
|
|
||||||
|
public rc_api_start_session_request_t(string username, string api_token, int game_id)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.game_id = game_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_award_achievement_response_t
|
||||||
|
{
|
||||||
|
public int awarded_achievement_id;
|
||||||
|
public int new_player_score;
|
||||||
|
public int achievements_remaining;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_achievement_definition_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int points;
|
||||||
|
public rc_runtime_achievement_category_t category;
|
||||||
|
public IntPtr title;
|
||||||
|
public IntPtr description;
|
||||||
|
public IntPtr definition;
|
||||||
|
public IntPtr author;
|
||||||
|
public IntPtr badge_name;
|
||||||
|
public long created; // time_t?
|
||||||
|
public long updated; // time_t?
|
||||||
|
|
||||||
|
public string Title => Mershul.PtrToStringUtf8(title);
|
||||||
|
public string Description => Mershul.PtrToStringUtf8(description);
|
||||||
|
public string Definition => Mershul.PtrToStringUtf8(definition);
|
||||||
|
public string Author => Mershul.PtrToStringUtf8(author);
|
||||||
|
public string BadgeName => Mershul.PtrToStringUtf8(badge_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_leaderboard_definition_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int format;
|
||||||
|
public IntPtr title;
|
||||||
|
public IntPtr description;
|
||||||
|
public IntPtr definition;
|
||||||
|
public int lower_is_better;
|
||||||
|
public int hidden;
|
||||||
|
|
||||||
|
public string Title => Mershul.PtrToStringUtf8(title);
|
||||||
|
public string Description => Mershul.PtrToStringUtf8(description);
|
||||||
|
public string Definition => Mershul.PtrToStringUtf8(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_game_data_response_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public RetroAchievements.ConsoleID console_id;
|
||||||
|
public IntPtr title;
|
||||||
|
public IntPtr image_name;
|
||||||
|
public IntPtr rich_presence_script;
|
||||||
|
public IntPtr achievements;
|
||||||
|
public int num_achievements;
|
||||||
|
public IntPtr leaderboards;
|
||||||
|
public int num_leaderboards;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
|
||||||
|
public string Title => Mershul.PtrToStringUtf8(title);
|
||||||
|
public string ImageName => Mershul.PtrToStringUtf8(image_name);
|
||||||
|
public string RichPresenceScript => Mershul.PtrToStringUtf8(rich_presence_script);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_ping_response_t
|
||||||
|
{
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_resolve_hash_response_t
|
||||||
|
{
|
||||||
|
public int game_id;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_submit_lboard_entry_response_t
|
||||||
|
{
|
||||||
|
public int submitted_score;
|
||||||
|
public int best_score;
|
||||||
|
public int new_rank;
|
||||||
|
public int num_entries;
|
||||||
|
public IntPtr top_entries;
|
||||||
|
public int num_top_entries;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_award_achievement_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int achievement_id;
|
||||||
|
public int hardcore;
|
||||||
|
public string game_hash;
|
||||||
|
|
||||||
|
public rc_api_award_achievement_request_t(string username, string api_token, int achievement_id, bool hardcore, string game_hash)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.achievement_id = achievement_id;
|
||||||
|
this.hardcore = hardcore ? 1 : 0;
|
||||||
|
this.game_hash = game_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_game_data_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int game_id;
|
||||||
|
|
||||||
|
public rc_api_fetch_game_data_request_t(string username, string api_token, int game_id)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.game_id = game_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_image_request_t
|
||||||
|
{
|
||||||
|
public string image_name;
|
||||||
|
public rc_api_image_type_t image_type;
|
||||||
|
|
||||||
|
public rc_api_fetch_image_request_t(string image_name, rc_api_image_type_t image_type)
|
||||||
|
{
|
||||||
|
this.image_name = image_name;
|
||||||
|
this.image_type = image_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_ping_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int game_id;
|
||||||
|
public string rich_presence;
|
||||||
|
|
||||||
|
public rc_api_ping_request_t(string username, string api_token, int game_id, string rich_presence)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.game_id = game_id;
|
||||||
|
this.rich_presence = rich_presence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_resolve_hash_request_t
|
||||||
|
{
|
||||||
|
public string username; // note: not actually used
|
||||||
|
public string api_token; // note: not actually used
|
||||||
|
public string game_hash;
|
||||||
|
|
||||||
|
public rc_api_resolve_hash_request_t(string username, string api_token, string game_hash)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.game_hash = game_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_submit_lboard_entry_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int leaderboard_id;
|
||||||
|
public int score;
|
||||||
|
public string game_hash;
|
||||||
|
|
||||||
|
public rc_api_submit_lboard_entry_request_t(string username, string api_token, int leaderboard_id, int score, string game_hash)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.leaderboard_id = leaderboard_id;
|
||||||
|
this.score = score;
|
||||||
|
this.game_hash = game_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_achievement_info_response_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int game_id;
|
||||||
|
public int num_awarded;
|
||||||
|
public int num_players;
|
||||||
|
public IntPtr recently_awarded;
|
||||||
|
public int num_recently_awarded;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_games_list_response_t
|
||||||
|
{
|
||||||
|
public IntPtr entries;
|
||||||
|
public int num_entries;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_leaderboard_info_response_t
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int format;
|
||||||
|
public int lower_is_better;
|
||||||
|
public IntPtr title;
|
||||||
|
public IntPtr description;
|
||||||
|
public IntPtr definition;
|
||||||
|
public int game_id;
|
||||||
|
public IntPtr author;
|
||||||
|
public long created; // time_t?
|
||||||
|
public long updated; // time_t?
|
||||||
|
public IntPtr entries;
|
||||||
|
public int num_entries;
|
||||||
|
public rc_api_response_t response;
|
||||||
|
|
||||||
|
public string Title => Mershul.PtrToStringUtf8(title);
|
||||||
|
public string Description => Mershul.PtrToStringUtf8(description);
|
||||||
|
public string Definition => Mershul.PtrToStringUtf8(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_achievement_info_request_t
|
||||||
|
{
|
||||||
|
public string username;
|
||||||
|
public string api_token;
|
||||||
|
public int achievement_id;
|
||||||
|
public int first_entry;
|
||||||
|
public int count;
|
||||||
|
public int friends_only;
|
||||||
|
|
||||||
|
public rc_api_fetch_achievement_info_request_t(string username, string api_token, int achievement_id, int first_entry, int count, int friends_only)
|
||||||
|
{
|
||||||
|
this.username = username;
|
||||||
|
this.api_token = api_token;
|
||||||
|
this.achievement_id = achievement_id;
|
||||||
|
this.first_entry = first_entry;
|
||||||
|
this.count = count;
|
||||||
|
this.friends_only = friends_only;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_games_list_request_t
|
||||||
|
{
|
||||||
|
public int console_id;
|
||||||
|
|
||||||
|
public rc_api_fetch_games_list_request_t(int console_id)
|
||||||
|
{
|
||||||
|
this.console_id = console_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct rc_api_fetch_leaderboard_info_request_t
|
||||||
|
{
|
||||||
|
public int leaderboard_id;
|
||||||
|
public int count;
|
||||||
|
public int first_entry;
|
||||||
|
public string username;
|
||||||
|
|
||||||
|
public rc_api_fetch_leaderboard_info_request_t(int leaderboard_id, int count, int first_entry, string username)
|
||||||
|
{
|
||||||
|
this.leaderboard_id = leaderboard_id;
|
||||||
|
this.count = count;
|
||||||
|
this.first_entry = first_entry;
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void rc_runtime_event_handler_t(IntPtr runtime_event);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate int rc_peek_t(int address, int num_bytes, IntPtr ud);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public delegate bool rc_runtime_validate_address_t(int address);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void rc_hash_message_callback(string message);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract IntPtr rc_console_memory_regions(RetroAchievements.ConsoleID id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract IntPtr rc_console_name(RetroAchievements.ConsoleID id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract IntPtr rc_error_str(rc_error_t error_code);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_init(ref rc_runtime_t runtime);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_destroy(ref rc_runtime_t runtime);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_reset(ref rc_runtime_t runtime);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_do_frame(ref rc_runtime_t runtime, rc_runtime_event_handler_t rc_runtime_event_handler_t, rc_peek_t peek, IntPtr ud, IntPtr unused);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_runtime_progress_size(ref rc_runtime_t runtime, IntPtr unused);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_serialize_progress(byte[] buffer, ref rc_runtime_t runtime, IntPtr unused);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_runtime_deserialize_progress(ref rc_runtime_t runtime, byte[] serialized, IntPtr unused);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_invalidate_address(ref rc_runtime_t runtime, int address);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_validate_addresses(ref rc_runtime_t runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_runtime_activate_achievement(ref rc_runtime_t runtime, int id, string memaddr, IntPtr unused, int unused_idx);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_runtime_activate_lboard(ref rc_runtime_t runtime, int id, string memaddr, IntPtr unused, int unused_idx);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_runtime_activate_richpresence(ref rc_runtime_t runtime, string script, IntPtr unused, int unused_idx);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract IntPtr rc_runtime_get_achievement(ref rc_runtime_t runtime, int id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract IntPtr rc_runtime_get_lboard(ref rc_runtime_t runtime, int id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract int rc_runtime_get_richpresence(ref rc_runtime_t runtime, byte[] buffer, int buffersize, rc_peek_t peek, IntPtr ud, IntPtr unused);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool rc_runtime_get_achievement_measured(ref rc_runtime_t runtime, int id, out int measured_value, out int measured_target);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract int rc_runtime_format_achievement_measured(ref rc_runtime_t runtime, int id, byte[] buffer, long buffer_size);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract int rc_runtime_format_lboard_value(byte[] buffer, int size, int value, int format);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_deactivate_achievement(ref rc_runtime_t runtime, int id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_runtime_deactivate_lboard(ref rc_runtime_t runtime, int id);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_init_error_message_callback(rc_hash_message_callback callback);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_init_custom_cdreader(IntPtr reader);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_init_custom_filereader(IntPtr reader);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool rc_hash_generate_from_buffer(byte[] hash, RetroAchievements.ConsoleID console_id, byte[] buffer, long buffer_size);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool rc_hash_generate_from_file(byte[] hash, RetroAchievements.ConsoleID console_id, string path);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_initialize_iterator(IntPtr iterator, string path, byte[] buffer, long buffer_size);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool rc_hash_iterate(byte[] hash, IntPtr iterator);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_hash_destroy_iterator(IntPtr iterator);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_set_host(string hostname);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_set_image_host(string hostname);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_login_request(out rc_api_request_t request, ref rc_api_login_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_start_session_request(out rc_api_request_t request, ref rc_api_start_session_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_resolve_hash_request(out rc_api_request_t request, ref rc_api_resolve_hash_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_game_data_request(out rc_api_request_t request, ref rc_api_fetch_game_data_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_user_unlocks_request(out rc_api_request_t request, ref rc_api_fetch_user_unlocks_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_achievement_info_request(out rc_api_request_t request, ref rc_api_fetch_achievement_info_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_leaderboard_info_request(out rc_api_request_t request, ref rc_api_fetch_leaderboard_info_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_image_request(out rc_api_request_t request, ref rc_api_fetch_image_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_award_achievement_request(out rc_api_request_t request, ref rc_api_award_achievement_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_submit_lboard_entry_request(out rc_api_request_t request, ref rc_api_submit_lboard_entry_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_ping_request(out rc_api_request_t request, ref rc_api_ping_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true)]
|
||||||
|
public abstract rc_error_t rc_api_init_fetch_games_list_request(out rc_api_request_t request, ref rc_api_fetch_games_list_request_t api_params);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_request(ref rc_api_request_t request);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_login_response(out rc_api_login_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_start_session_response(out rc_api_start_session_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_resolve_hash_response(out rc_api_resolve_hash_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_fetch_game_data_response(out rc_api_fetch_game_data_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_fetch_user_unlocks_response(out rc_api_fetch_user_unlocks_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_fetch_achievement_info_response(out rc_api_fetch_achievement_info_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_fetch_leaderboard_info_response(out rc_api_fetch_leaderboard_info_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_award_achievement_response(out rc_api_award_achievement_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_submit_lboard_entry_response(out rc_api_submit_lboard_entry_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_ping_response(out rc_api_ping_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract rc_error_t rc_api_process_fetch_games_list_response(out rc_api_fetch_games_list_response_t response, byte[] server_response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_login_response(ref rc_api_login_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_start_session_response(ref rc_api_start_session_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_resolve_hash_response(ref rc_api_resolve_hash_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_fetch_game_data_response(ref rc_api_fetch_game_data_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_fetch_user_unlocks_response(ref rc_api_fetch_user_unlocks_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_fetch_achievement_info_response(ref rc_api_fetch_achievement_info_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_fetch_leaderboard_info_response(ref rc_api_fetch_leaderboard_info_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_award_achievement_response(ref rc_api_award_achievement_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_submit_lboard_entry_response(ref rc_api_submit_lboard_entry_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_ping_response(ref rc_api_ping_response_t response);
|
||||||
|
|
||||||
|
[BizImport(cc)]
|
||||||
|
public abstract void rc_api_destroy_fetch_games_list_response(ref rc_api_fetch_games_list_response_t response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
using BizHawk.BizInvoke;
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RAIntegration
|
||||||
|
{
|
||||||
|
private static DynamicLibraryImportResolver _resolver;
|
||||||
|
private static Version _version;
|
||||||
|
|
||||||
|
private static void AttachDll()
|
||||||
|
{
|
||||||
|
_resolver = new("RA_Integration-x64.dll", hasLimitedLifetime: true);
|
||||||
|
RA = BizInvoker.GetInvoker<RAInterface>(_resolver, CallingConventionAdapters.Native);
|
||||||
|
_version = new(Marshal.PtrToStringAnsi(RA.IntegrationVersion()));
|
||||||
|
Console.WriteLine($"Loaded RetroAchievements v{_version}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DetachDll()
|
||||||
|
{
|
||||||
|
RA?.Shutdown();
|
||||||
|
_resolver?.Dispose();
|
||||||
|
_resolver = null;
|
||||||
|
RA = null;
|
||||||
|
_version = new(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DownloadDll(string url)
|
||||||
|
{
|
||||||
|
if (url.StartsWith("http:"))
|
||||||
|
{
|
||||||
|
// force https
|
||||||
|
url = url.Replace("http:", "https:");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var downloadForm = new RAIntegrationDownloaderForm(url);
|
||||||
|
downloadForm.ShowDialog();
|
||||||
|
return downloadForm.DownloadSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CheckUpdateRA(IMainFormForRetroAchievements mainForm)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var http = new HttpCommunication(null, "https://retroachievements.org/dorequest.php?r=latestintegration", null);
|
||||||
|
var info = JsonConvert.DeserializeObject<Dictionary<string, object>>(http.ExecGet());
|
||||||
|
if (info.TryGetValue("Success", out var success) && (bool)success)
|
||||||
|
{
|
||||||
|
var lastestVer = new Version((string)info["LatestVersion"]);
|
||||||
|
var minVer = new Version((string)info["MinimumVersion"]);
|
||||||
|
|
||||||
|
if (_version < minVer)
|
||||||
|
{
|
||||||
|
if (mainForm.ShowMessageBox2(
|
||||||
|
owner: null,
|
||||||
|
text: "An update is required to use RetroAchievements. Do you want to download the update now?",
|
||||||
|
caption: "Update",
|
||||||
|
icon: EMsgBoxIcon.Question,
|
||||||
|
useOKCancel: false))
|
||||||
|
{
|
||||||
|
DetachDll();
|
||||||
|
var ret = DownloadDll((string)info["LatestVersionUrlX64"]);
|
||||||
|
AttachDll();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_version < lastestVer)
|
||||||
|
{
|
||||||
|
if (mainForm.ShowMessageBox2(
|
||||||
|
owner: null,
|
||||||
|
text: "An optional update is available for RetroAchievements. Do you want to download the update now?",
|
||||||
|
caption: "Update",
|
||||||
|
icon: EMsgBoxIcon.Question,
|
||||||
|
useOKCancel: false))
|
||||||
|
{
|
||||||
|
DetachDll();
|
||||||
|
DownloadDll((string)info["LatestVersionUrlX64"]);
|
||||||
|
AttachDll();
|
||||||
|
return true; // even if this fails, should be OK to use the old dll
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// don't have to update in this case
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainForm.ShowMessageBox(
|
||||||
|
owner: null,
|
||||||
|
text: "Failed to fetch update information, cannot start RetroAchievements.",
|
||||||
|
caption: "Error",
|
||||||
|
icon: EMsgBoxIcon.Error);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// is this needed?
|
||||||
|
mainForm.ShowMessageBox(
|
||||||
|
owner: null,
|
||||||
|
text: $"Exception {ex.Message} occurred when fetching update information, cannot start RetroAchievements.",
|
||||||
|
caption: "Error",
|
||||||
|
icon: EMsgBoxIcon.Error);
|
||||||
|
|
||||||
|
DetachDll();
|
||||||
|
AttachDll();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RAIntegration : RetroAchievements
|
||||||
|
{
|
||||||
|
private static RAInterface RA;
|
||||||
|
public static bool IsAvailable => RA != null;
|
||||||
|
|
||||||
|
static RAIntegration()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (OSTailoredCode.IsUnixHost)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("RAIntegration is Windows only!");
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachDll();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
DetachDll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly RAInterface.IsActiveDelegate _isActive;
|
||||||
|
private readonly RAInterface.UnpauseDelegate _unpause;
|
||||||
|
private readonly RAInterface.PauseDelegate _pause;
|
||||||
|
private readonly RAInterface.RebuildMenuDelegate _rebuildMenu;
|
||||||
|
private readonly RAInterface.EstimateTitleDelegate _estimateTitle;
|
||||||
|
private readonly RAInterface.ResetEmulatorDelegate _resetEmulator;
|
||||||
|
private readonly RAInterface.LoadROMDelegate _loadROM;
|
||||||
|
|
||||||
|
private readonly RAInterface.MenuItem[] _menuItems = new RAInterface.MenuItem[40];
|
||||||
|
|
||||||
|
// Memory may be accessed by another thread (for rich presence)
|
||||||
|
// and peeks for us are not thread safe, so we need to guard it
|
||||||
|
private readonly AutoResetEvent _memAccessReady = new(false);
|
||||||
|
private readonly AutoResetEvent _memAccessGo = new(false);
|
||||||
|
private readonly AutoResetEvent _memAccessDone = new(false);
|
||||||
|
private readonly RAMemGuard _memGuard;
|
||||||
|
|
||||||
|
private void RebuildMenu()
|
||||||
|
{
|
||||||
|
var numItems = RA.GetPopupMenuItems(_menuItems);
|
||||||
|
var tsmiddi = _raDropDownItems;
|
||||||
|
tsmiddi.Clear();
|
||||||
|
{
|
||||||
|
var tsi = new ToolStripMenuItem("Shutdown RetroAchievements");
|
||||||
|
tsi.Click += (_, _) => _shutdownRACallback();
|
||||||
|
tsmiddi.Add(tsi);
|
||||||
|
|
||||||
|
tsi = new ToolStripMenuItem("Autostart RetroAchievements")
|
||||||
|
{
|
||||||
|
Checked = _getConfig().RAAutostart,
|
||||||
|
CheckOnClick = true,
|
||||||
|
};
|
||||||
|
tsi.CheckedChanged += (_, _) => _getConfig().RAAutostart ^= true;
|
||||||
|
tsmiddi.Add(tsi);
|
||||||
|
|
||||||
|
var tss = new ToolStripSeparator();
|
||||||
|
tsmiddi.Add(tss);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < numItems; i++)
|
||||||
|
{
|
||||||
|
if (_menuItems[i].Label != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var tsi = new ToolStripMenuItem(Marshal.PtrToStringUni(_menuItems[i].Label))
|
||||||
|
{
|
||||||
|
Checked = _menuItems[i].Checked != 0,
|
||||||
|
};
|
||||||
|
var id = _menuItems[i].ID;
|
||||||
|
tsi.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
RA.InvokeDialog(id);
|
||||||
|
_mainForm.UpdateWindowTitle();
|
||||||
|
};
|
||||||
|
tsmiddi.Add(tsi);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tss = new ToolStripSeparator();
|
||||||
|
tsmiddi.Add(tss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleHardcoreModeDisable(string reason)
|
||||||
|
{
|
||||||
|
_mainForm.ShowMessageBox(null, $"{reason} Disabling hardcore mode.", "Warning", EMsgBoxIcon.Warning);
|
||||||
|
RA.WarnDisableHardcore(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int IdentifyHash(string hash)
|
||||||
|
=> RA.IdentifyHash(hash);
|
||||||
|
|
||||||
|
protected override int IdentifyRom(byte[] rom)
|
||||||
|
=> RA.IdentifyRom(rom, rom.Length);
|
||||||
|
|
||||||
|
public RAIntegration(IMainFormForRetroAchievements mainForm, InputManager inputManager, ToolManager tools,
|
||||||
|
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
|
||||||
|
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
|
||||||
|
{
|
||||||
|
_memGuard = new(_memAccessReady, _memAccessGo, _memAccessDone);
|
||||||
|
|
||||||
|
RA.InitClient(_mainForm.Handle, "BizHawk", VersionInfo.GetEmuVersion());
|
||||||
|
|
||||||
|
_isActive = () => !Emu.IsNull();
|
||||||
|
_unpause = _mainForm.UnpauseEmulator;
|
||||||
|
_pause = _mainForm.PauseEmulator;
|
||||||
|
_rebuildMenu = RebuildMenu;
|
||||||
|
_estimateTitle = buffer =>
|
||||||
|
{
|
||||||
|
var name = Encoding.UTF8.GetBytes(Game?.Name ?? "No Game Info Available");
|
||||||
|
Marshal.Copy(name, 0, buffer, Math.Min(name.Length, 256));
|
||||||
|
};
|
||||||
|
_resetEmulator = () => _mainForm.RebootCore();
|
||||||
|
_loadROM = path => _mainForm.LoadRom(path, new LoadRomArgs { OpenAdvanced = OpenAdvancedSerializer.ParseWithLegacy(path) });
|
||||||
|
|
||||||
|
RA.InstallSharedFunctionsExt(_isActive, _unpause, _pause, _rebuildMenu, _estimateTitle, _resetEmulator, _loadROM);
|
||||||
|
|
||||||
|
RA.AttemptLogin(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
RA?.Shutdown();
|
||||||
|
_memGuard.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSaveState(string path)
|
||||||
|
=> RA.OnSaveState(path);
|
||||||
|
|
||||||
|
public override void OnLoadState(string path)
|
||||||
|
{
|
||||||
|
if (RA.HardcoreModeIsActive())
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RA.OnLoadState(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Stop()
|
||||||
|
{
|
||||||
|
RA.ClearMemoryBanks();
|
||||||
|
RA.ActivateGame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Restart()
|
||||||
|
{
|
||||||
|
var consoleId = SystemIdToConsoleId();
|
||||||
|
RA.SetConsoleID(consoleId);
|
||||||
|
|
||||||
|
RA.ClearMemoryBanks();
|
||||||
|
|
||||||
|
if (Emu.HasMemoryDomains())
|
||||||
|
{
|
||||||
|
_memFunctions = CreateMemoryBanks(consoleId, Domains, Emu.CanDebug() ? Emu.AsDebuggable() : null);
|
||||||
|
|
||||||
|
for (int i = 0; i < _memFunctions.Count; i++)
|
||||||
|
{
|
||||||
|
_memFunctions[i].MemGuard = _memGuard;
|
||||||
|
RA.InstallMemoryBank(i, _memFunctions[i].ReadFunc, _memFunctions[i].WriteFunc, _memFunctions[i].BankSize);
|
||||||
|
RA.InstallMemoryBankBlockReader(i, _memFunctions[i].ReadBlockFunc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AllGamesVerified = true;
|
||||||
|
|
||||||
|
if (_mainForm.CurrentlyOpenRomArgs is not null)
|
||||||
|
{
|
||||||
|
var ids = GetRAGameIds(_mainForm.CurrentlyOpenRomArgs.OpenAdvanced, consoleId);
|
||||||
|
|
||||||
|
AllGamesVerified = !ids.Contains(0);
|
||||||
|
|
||||||
|
RA.ActivateGame(ids.Count > 0 ? ids[0] : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RA.ActivateGame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Update();
|
||||||
|
RebuildMenu();
|
||||||
|
|
||||||
|
// workaround a bug in RA which will cause the window title to be changed despite us not calling UpdateAppTitle
|
||||||
|
_mainForm.UpdateWindowTitle();
|
||||||
|
|
||||||
|
// note: this can only catch quicksaves (probably only case of accidential use from hotkeys)
|
||||||
|
_mainForm.EmuClient.BeforeQuickLoad += (_, e) =>
|
||||||
|
{
|
||||||
|
if (RA.HardcoreModeIsActive())
|
||||||
|
{
|
||||||
|
e.Handled = !RA.WarnDisableHardcore("load a quicksave");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
if (RA.HardcoreModeIsActive())
|
||||||
|
{
|
||||||
|
CheckHardcoreModeConditions();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_inputManager.ClientControls["Open RA Overlay"])
|
||||||
|
{
|
||||||
|
RA.SetPaused(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RA.IsOverlayFullyVisible())
|
||||||
|
{
|
||||||
|
var ci = new RAInterface.ControllerInput
|
||||||
|
{
|
||||||
|
UpPressed = _inputManager.ClientControls["RA Up"],
|
||||||
|
DownPressed = _inputManager.ClientControls["RA Down"],
|
||||||
|
LeftPressed = _inputManager.ClientControls["RA Left"],
|
||||||
|
RightPressed = _inputManager.ClientControls["RA Right"],
|
||||||
|
ConfirmPressed = _inputManager.ClientControls["RA Confirm"],
|
||||||
|
CancelPressed = _inputManager.ClientControls["RA Cancel"],
|
||||||
|
QuitPressed = _inputManager.ClientControls["RA Quit"],
|
||||||
|
};
|
||||||
|
|
||||||
|
RA.NavigateOverlay(ref ci);
|
||||||
|
|
||||||
|
// todo: suppress user inputs with overlay active?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_memAccessReady.WaitOne(0))
|
||||||
|
{
|
||||||
|
_memAccessGo.Set();
|
||||||
|
_memAccessDone.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameAdvance()
|
||||||
|
{
|
||||||
|
var input = _inputManager.ControllerOutput;
|
||||||
|
foreach (var resetButton in input.Definition.BoolButtons.Where(b => b.Contains("Power") || b.Contains("Reset")))
|
||||||
|
{
|
||||||
|
if (input.IsPressed(resetButton))
|
||||||
|
{
|
||||||
|
RA.OnReset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Emu.HasMemoryDomains())
|
||||||
|
{
|
||||||
|
// we want to EnterExit to prevent wbx host spam when peeks are spammed
|
||||||
|
using (Domains.MainMemory.EnterExit())
|
||||||
|
{
|
||||||
|
RA.DoAchievementsFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RA.DoAchievementsFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs
generated
Normal file
170
src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RAIntegrationDownloaderForm
|
||||||
|
{
|
||||||
|
/// <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.btnCancel = new System.Windows.Forms.Button();
|
||||||
|
this.btnDownload = new System.Windows.Forms.Button();
|
||||||
|
this.label3 = new BizHawk.WinForms.Controls.LocLabelEx();
|
||||||
|
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
|
||||||
|
this.progressBar1 = new System.Windows.Forms.ProgressBar();
|
||||||
|
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||||
|
this.txtLocation = new System.Windows.Forms.TextBox();
|
||||||
|
this.label1 = new System.Windows.Forms.Label();
|
||||||
|
this.label2 = new System.Windows.Forms.Label();
|
||||||
|
this.txtUrl = new System.Windows.Forms.TextBox();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// 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(667, 86);
|
||||||
|
this.btnCancel.Name = "btnCancel";
|
||||||
|
this.btnCancel.Size = new System.Drawing.Size(75, 23);
|
||||||
|
this.btnCancel.TabIndex = 7;
|
||||||
|
this.btnCancel.Text = "Cancel";
|
||||||
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
|
//
|
||||||
|
// btnDownload
|
||||||
|
//
|
||||||
|
this.btnDownload.Location = new System.Drawing.Point(9, 86);
|
||||||
|
this.btnDownload.Name = "btnDownload";
|
||||||
|
this.btnDownload.Size = new System.Drawing.Size(186, 23);
|
||||||
|
this.btnDownload.TabIndex = 6;
|
||||||
|
this.btnDownload.Text = "Download";
|
||||||
|
this.btnDownload.UseVisualStyleBackColor = true;
|
||||||
|
this.btnDownload.Click += new System.EventHandler(this.btnDownload_Click);
|
||||||
|
//
|
||||||
|
// label3
|
||||||
|
//
|
||||||
|
this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.label3.Location = new System.Drawing.Point(6, -35);
|
||||||
|
this.label3.MaximumSize = new System.Drawing.Size(260, 0);
|
||||||
|
this.label3.Name = "label3";
|
||||||
|
//
|
||||||
|
// linkLabel1
|
||||||
|
//
|
||||||
|
this.linkLabel1.AutoSize = true;
|
||||||
|
this.linkLabel1.Location = new System.Drawing.Point(715, 61);
|
||||||
|
this.linkLabel1.Name = "linkLabel1";
|
||||||
|
this.linkLabel1.Size = new System.Drawing.Size(27, 13);
|
||||||
|
this.linkLabel1.TabIndex = 12;
|
||||||
|
this.linkLabel1.TabStop = true;
|
||||||
|
this.linkLabel1.Text = "Link";
|
||||||
|
this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
|
||||||
|
//
|
||||||
|
// progressBar1
|
||||||
|
//
|
||||||
|
this.progressBar1.Location = new System.Drawing.Point(201, 86);
|
||||||
|
this.progressBar1.Name = "progressBar1";
|
||||||
|
this.progressBar1.Size = new System.Drawing.Size(186, 23);
|
||||||
|
this.progressBar1.TabIndex = 13;
|
||||||
|
//
|
||||||
|
// timer1
|
||||||
|
//
|
||||||
|
this.timer1.Enabled = true;
|
||||||
|
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
|
||||||
|
//
|
||||||
|
// txtLocation
|
||||||
|
//
|
||||||
|
this.txtLocation.Location = new System.Drawing.Point(95, 12);
|
||||||
|
this.txtLocation.Name = "txtLocation";
|
||||||
|
this.txtLocation.ReadOnly = true;
|
||||||
|
this.txtLocation.Size = new System.Drawing.Size(613, 20);
|
||||||
|
this.txtLocation.TabIndex = 15;
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
this.label1.AutoSize = true;
|
||||||
|
this.label1.Location = new System.Drawing.Point(9, 15);
|
||||||
|
this.label1.Name = "label1";
|
||||||
|
this.label1.Size = new System.Drawing.Size(80, 13);
|
||||||
|
this.label1.TabIndex = 16;
|
||||||
|
this.label1.Text = "Local Location:";
|
||||||
|
//
|
||||||
|
// label2
|
||||||
|
//
|
||||||
|
this.label2.AutoSize = true;
|
||||||
|
this.label2.Location = new System.Drawing.Point(57, 41);
|
||||||
|
this.label2.Name = "label2";
|
||||||
|
this.label2.Size = new System.Drawing.Size(32, 13);
|
||||||
|
this.label2.TabIndex = 17;
|
||||||
|
this.label2.Text = "URL:";
|
||||||
|
//
|
||||||
|
// txtUrl
|
||||||
|
//
|
||||||
|
this.txtUrl.Location = new System.Drawing.Point(96, 38);
|
||||||
|
this.txtUrl.Name = "txtUrl";
|
||||||
|
this.txtUrl.ReadOnly = true;
|
||||||
|
this.txtUrl.Size = new System.Drawing.Size(613, 20);
|
||||||
|
this.txtUrl.TabIndex = 18;
|
||||||
|
//
|
||||||
|
// RAIntegrationDownloaderForm
|
||||||
|
//
|
||||||
|
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(754, 121);
|
||||||
|
this.Controls.Add(this.progressBar1);
|
||||||
|
this.Controls.Add(this.txtUrl);
|
||||||
|
this.Controls.Add(this.btnDownload);
|
||||||
|
this.Controls.Add(this.label2);
|
||||||
|
this.Controls.Add(this.linkLabel1);
|
||||||
|
this.Controls.Add(this.label1);
|
||||||
|
this.Controls.Add(this.txtLocation);
|
||||||
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.Controls.Add(this.label3);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(300, 160);
|
||||||
|
this.Name = "RAIntegrationDownloaderForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Download RAIntegration";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private BizHawk.WinForms.Controls.LocLabelEx label3;
|
||||||
|
private System.Windows.Forms.Button btnCancel;
|
||||||
|
private System.Windows.Forms.Button btnDownload;
|
||||||
|
private System.Windows.Forms.LinkLabel linkLabel1;
|
||||||
|
private System.Windows.Forms.ProgressBar progressBar1;
|
||||||
|
private System.Windows.Forms.Timer timer1;
|
||||||
|
private System.Windows.Forms.TextBox txtLocation;
|
||||||
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.Label label2;
|
||||||
|
private System.Windows.Forms.TextBox txtUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Common.PathExtensions;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads RAIntegration (largely a copy paste of ffmpeg's downloader)
|
||||||
|
/// </summary>
|
||||||
|
public partial class RAIntegrationDownloaderForm : Form
|
||||||
|
{
|
||||||
|
public RAIntegrationDownloaderForm(string url)
|
||||||
|
{
|
||||||
|
_path = Path.Combine(PathUtils.DataDirectoryPath, "dll", "RA_Integration-x64.dll");
|
||||||
|
_url = url;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
txtLocation.Text = _path;
|
||||||
|
txtUrl.Text = _url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly string _url;
|
||||||
|
|
||||||
|
private int _pct = 0;
|
||||||
|
private bool _exiting = false;
|
||||||
|
private bool _succeeded = false;
|
||||||
|
private bool _failed = false;
|
||||||
|
private Thread _thread;
|
||||||
|
|
||||||
|
public bool DownloadSucceeded()
|
||||||
|
{
|
||||||
|
// block until the thread dies
|
||||||
|
while (_thread?.IsAlive ?? false)
|
||||||
|
{
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThreadProc()
|
||||||
|
{
|
||||||
|
Download();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Download()
|
||||||
|
{
|
||||||
|
//the temp file is owned by this thread
|
||||||
|
var fn = TempFileManager.GetTempFilename("RAIntegration_download", ".dll", false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var evt = new ManualResetEvent(false))
|
||||||
|
{
|
||||||
|
using var client = new System.Net.WebClient();
|
||||||
|
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
|
||||||
|
client.DownloadFileAsync(new Uri(_url), fn);
|
||||||
|
client.DownloadProgressChanged += (object sender, System.Net.DownloadProgressChangedEventArgs e) =>
|
||||||
|
{
|
||||||
|
_pct = e.ProgressPercentage;
|
||||||
|
};
|
||||||
|
client.DownloadFileCompleted += (object sender, System.ComponentModel.AsyncCompletedEventArgs e) =>
|
||||||
|
{
|
||||||
|
//we don't really need a status. we'll just try to unzip it when it's done
|
||||||
|
evt.Set();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
if (evt.WaitOne(10))
|
||||||
|
break;
|
||||||
|
|
||||||
|
//if the gui thread ordered an exit, cancel the download and wait for it to acknowledge
|
||||||
|
if (_exiting)
|
||||||
|
{
|
||||||
|
client.CancelAsync();
|
||||||
|
evt.WaitOne();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//throw new Exception("test of download failure");
|
||||||
|
|
||||||
|
//if we were ordered to exit, bail without wasting any more time
|
||||||
|
if (_exiting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//try acquiring file
|
||||||
|
using (var dll = new HawkFile(fn))
|
||||||
|
{
|
||||||
|
var data = dll!.ReadAllBytes();
|
||||||
|
|
||||||
|
//last chance. exiting, don't dump the new RAIntegration file
|
||||||
|
if (_exiting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!);
|
||||||
|
if (!parentDir.Exists) parentDir.Create();
|
||||||
|
if (File.Exists(_path)) File.Delete(_path);
|
||||||
|
File.WriteAllBytes(_path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_succeeded = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_failed = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { File.Delete(fn); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnDownload_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
btnDownload.Text = "Downloading...";
|
||||||
|
btnDownload.Enabled = false;
|
||||||
|
_failed = false;
|
||||||
|
_succeeded = false;
|
||||||
|
_pct = 0;
|
||||||
|
_thread = new Thread(ThreadProc);
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnCancel_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
//inform the worker thread that it needs to try terminating without doing anything else
|
||||||
|
//(it will linger on in background for a bit til it can service this)
|
||||||
|
_exiting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timer1_Tick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
//if it's done, close the window. the user will be smart enough to reopen it
|
||||||
|
if (_succeeded)
|
||||||
|
Close();
|
||||||
|
if (_failed)
|
||||||
|
{
|
||||||
|
_failed = false;
|
||||||
|
_pct = 0;
|
||||||
|
btnDownload.Text = "FAILED - Download Again";
|
||||||
|
btnDownload.Enabled = true;
|
||||||
|
}
|
||||||
|
progressBar1.Value = _pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start(_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?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="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>17, 17</value>
|
||||||
|
</metadata>
|
||||||
|
</root>
|
|
@ -0,0 +1,194 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using BizHawk.BizInvoke;
|
||||||
|
|
||||||
|
// this is largely a C# mirror of https://github.com/RetroAchievements/RAInterface
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract class RAInterface
|
||||||
|
{
|
||||||
|
private const CallingConvention cc = CallingConvention.Cdecl;
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_IntegrationVersion")]
|
||||||
|
public abstract IntPtr IntegrationVersion();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_HostName")]
|
||||||
|
public abstract IntPtr HostName();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_HostUrl")]
|
||||||
|
public abstract IntPtr HostUrl();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InitI")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool InitI(IntPtr hwnd, int emuID, string clientVer);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InitOffline")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool InitOffline(IntPtr hwnd, int emuID, string clientVer);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InitClient")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool InitClient(IntPtr hwnd, string clientName, string clientVer);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InitClientOffline")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool InitClientOffline(IntPtr hwnd, string clientName, string clientVer);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate bool IsActiveDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void UnpauseDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void PauseDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void RebuildMenuDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void EstimateTitleDelegate(IntPtr buffer);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void ResetEmulatorDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(cc)]
|
||||||
|
public delegate void LoadROMDelegate(string filename);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InstallSharedFunctionsExt")]
|
||||||
|
public abstract void InstallSharedFunctionsExt(IsActiveDelegate isActive,
|
||||||
|
UnpauseDelegate unpause, PauseDelegate pause, RebuildMenuDelegate rebuildMenu,
|
||||||
|
EstimateTitleDelegate estimateTitle, ResetEmulatorDelegate resetEmulator, LoadROMDelegate loadROM);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true, EntryPoint = "_RA_SetForceRepaint")]
|
||||||
|
public abstract void SetForceRepaint([MarshalAs(UnmanagedType.Bool)] bool enable);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_CreatePopupMenu")]
|
||||||
|
public abstract IntPtr CreatePopupMenu();
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct MenuItem
|
||||||
|
{
|
||||||
|
public IntPtr Label;
|
||||||
|
public IntPtr ID;
|
||||||
|
public int Checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_GetPopupMenuItems")]
|
||||||
|
public abstract int GetPopupMenuItems(MenuItem[] items);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InvokeDialog")]
|
||||||
|
public abstract void InvokeDialog(IntPtr lparam);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_SetUserAgentDetail")]
|
||||||
|
public abstract void SetUserAgentDetail(string detail);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true, EntryPoint = "_RA_AttemptLogin")]
|
||||||
|
public abstract void AttemptLogin([MarshalAs(UnmanagedType.Bool)] bool blocking);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_SetConsoleID")]
|
||||||
|
public abstract void SetConsoleID(RetroAchievements.ConsoleID consoleID);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_ClearMemoryBanks")]
|
||||||
|
public abstract void ClearMemoryBanks();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InstallMemoryBank")]
|
||||||
|
public abstract void InstallMemoryBank(int bankID, RetroAchievements.ReadMemoryFunc reader, RetroAchievements.WriteMemoryFunc writer, int bankSize);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_InstallMemoryBankBlockReader")]
|
||||||
|
public abstract void InstallMemoryBankBlockReader(int bankID, RetroAchievements.ReadMemoryBlockFunc reader);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_Shutdown")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool Shutdown();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_IsOverlayFullyVisible")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool IsOverlayFullyVisible();
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true, EntryPoint = "_RA_SetPaused")]
|
||||||
|
public abstract void SetPaused([MarshalAs(UnmanagedType.Bool)] bool isPaused);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct ControllerInput
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool UpPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool DownPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool LeftPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool RightPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool ConfirmPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool CancelPressed;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool QuitPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true, EntryPoint = "_RA_NavigateOverlay")]
|
||||||
|
public abstract void NavigateOverlay(ref ControllerInput input);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_UpdateHWnd")]
|
||||||
|
public abstract void UpdateHWnd(IntPtr hwnd);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_IdentifyRom")]
|
||||||
|
public abstract int IdentifyRom(byte[] rom, int romSize);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_IdentifyHash")]
|
||||||
|
public abstract int IdentifyHash(string hash);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_ActivateGame")]
|
||||||
|
public abstract void ActivateGame(int gameId);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_OnLoadNewRom")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool OnLoadNewRom(byte[] rom, int romSize);
|
||||||
|
|
||||||
|
[BizImport(cc, Compatibility = true, EntryPoint = "_RA_ConfirmLoadNewRom")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool ConfirmLoadNewRom([MarshalAs(UnmanagedType.Bool)] bool quitting);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_DoAchievementsFrame")]
|
||||||
|
public abstract void DoAchievementsFrame();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_SuspendRepaint")]
|
||||||
|
public abstract void SuspendRepaint();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_ResumeRepaint")]
|
||||||
|
public abstract void ResumeRepaint();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_UpdateAppTitle")]
|
||||||
|
public abstract void UpdateAppTitle(string message);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_UserName")]
|
||||||
|
public abstract IntPtr UserName();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_HardcoreModeIsActive")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool HardcoreModeIsActive();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_WarnDisableHardcore")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public abstract bool WarnDisableHardcore(string activity);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_OnReset")]
|
||||||
|
public abstract void OnReset();
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_OnSaveState")]
|
||||||
|
public abstract void OnSaveState(string filename);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_OnLoadState")]
|
||||||
|
public abstract void OnLoadState(string filename);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_CaptureState")]
|
||||||
|
public abstract int CaptureState(IntPtr buffer, int bufferSize);
|
||||||
|
|
||||||
|
[BizImport(cc, EntryPoint = "_RA_RestoreState")]
|
||||||
|
public abstract void RestoreState(IntPtr buffer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private readonly RCheevosAchievementListForm _cheevoListForm = new();
|
||||||
|
|
||||||
|
private bool CheevosActive { get; set; }
|
||||||
|
private bool AllowUnofficialCheevos { get; set; }
|
||||||
|
|
||||||
|
public class Cheevo
|
||||||
|
{
|
||||||
|
public int ID { get; }
|
||||||
|
public int Points { get; }
|
||||||
|
public LibRCheevos.rc_runtime_achievement_category_t Category { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string Definition { get; }
|
||||||
|
public string Author { get; }
|
||||||
|
private string BadgeName { get; }
|
||||||
|
public Bitmap BadgeUnlocked { get; private set; }
|
||||||
|
public Bitmap BadgeLocked { get; private set; }
|
||||||
|
public DateTime Created { get; }
|
||||||
|
public DateTime Updated { get; }
|
||||||
|
|
||||||
|
public bool IsSoftcoreUnlocked { get; set; }
|
||||||
|
public bool IsHardcoreUnlocked { get; set; }
|
||||||
|
|
||||||
|
public bool IsUnlocked(bool hardcore)
|
||||||
|
=> hardcore ? IsHardcoreUnlocked : IsSoftcoreUnlocked;
|
||||||
|
|
||||||
|
public void SetUnlocked(bool hardcore, bool unlocked)
|
||||||
|
{
|
||||||
|
if (hardcore)
|
||||||
|
{
|
||||||
|
IsHardcoreUnlocked = unlocked;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsSoftcoreUnlocked = unlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPrimed { get; set; }
|
||||||
|
private Func<bool> AllowUnofficialCheevos { get; }
|
||||||
|
public bool Invalid { get; set; }
|
||||||
|
public bool IsEnabled => !Invalid && (IsOfficial || AllowUnofficialCheevos());
|
||||||
|
public bool IsOfficial => Category is LibRCheevos.rc_runtime_achievement_category_t.RC_ACHIEVEMENT_CATEGORY_CORE;
|
||||||
|
|
||||||
|
public async void LoadImages()
|
||||||
|
{
|
||||||
|
BadgeUnlocked = await GetImage(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT).ConfigureAwait(false);
|
||||||
|
BadgeLocked = await GetImage(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cheevo(in LibRCheevos.rc_api_achievement_definition_t cheevo, Func<bool> allowUnofficialCheevos)
|
||||||
|
{
|
||||||
|
ID = cheevo.id;
|
||||||
|
Points = cheevo.points;
|
||||||
|
Category = cheevo.category;
|
||||||
|
Title = cheevo.Title;
|
||||||
|
Description = cheevo.Description;
|
||||||
|
Definition = cheevo.Definition;
|
||||||
|
Author = cheevo.Author;
|
||||||
|
BadgeName = cheevo.BadgeName;
|
||||||
|
BadgeUnlocked = null;
|
||||||
|
BadgeLocked = null;
|
||||||
|
Created = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(cheevo.created).ToLocalTime();
|
||||||
|
Updated = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(cheevo.updated).ToLocalTime();
|
||||||
|
IsSoftcoreUnlocked = false;
|
||||||
|
IsHardcoreUnlocked = false;
|
||||||
|
IsPrimed = false;
|
||||||
|
AllowUnofficialCheevos = allowUnofficialCheevos;
|
||||||
|
Invalid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cheevo(in Cheevo cheevo, Func<bool> allowUnofficialCheevos)
|
||||||
|
{
|
||||||
|
ID = cheevo.ID;
|
||||||
|
Points = cheevo.Points;
|
||||||
|
Category = cheevo.Category;
|
||||||
|
Title = cheevo.Title;
|
||||||
|
Description = cheevo.Description;
|
||||||
|
Definition = cheevo.Definition;
|
||||||
|
Author = cheevo.Author;
|
||||||
|
BadgeName = cheevo.BadgeName;
|
||||||
|
BadgeUnlocked = null;
|
||||||
|
BadgeLocked = null;
|
||||||
|
Created = cheevo.Created;
|
||||||
|
Updated = cheevo.Updated;
|
||||||
|
IsSoftcoreUnlocked = false;
|
||||||
|
IsHardcoreUnlocked = false;
|
||||||
|
IsPrimed = false;
|
||||||
|
AllowUnofficialCheevos = allowUnofficialCheevos;
|
||||||
|
Invalid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly byte[] _cheevoFormatBuffer = new byte[1024];
|
||||||
|
|
||||||
|
public string GetCheevoProgress(int id)
|
||||||
|
{
|
||||||
|
var len = _lib.rc_runtime_format_achievement_measured(ref _runtime, id, _cheevoFormatBuffer, _cheevoFormatBuffer.Length);
|
||||||
|
return Encoding.ASCII.GetString(_cheevoFormatBuffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleUnofficialCheevos()
|
||||||
|
{
|
||||||
|
if (_gameData.GameID == 0)
|
||||||
|
{
|
||||||
|
AllowUnofficialCheevos ^= true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var initReady = HardcoreMode ? _gameData.HardcoreInitUnlocksReady : _gameData.SoftcoreInitUnlocksReady;
|
||||||
|
initReady.WaitOne();
|
||||||
|
|
||||||
|
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
if (cheevo.IsEnabled && !cheevo.IsUnlocked(HardcoreMode))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_deactivate_achievement(ref _runtime, cheevo.ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AllowUnofficialCheevos ^= true;
|
||||||
|
|
||||||
|
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
if (cheevo.IsEnabled && !cheevo.IsUnlocked(HardcoreMode))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_activate_achievement(ref _runtime, cheevo.ID, cheevo.Definition, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToSoftcoreMode()
|
||||||
|
{
|
||||||
|
if (_gameData == null || _gameData.GameID == 0) return;
|
||||||
|
|
||||||
|
_gameData.SoftcoreInitUnlocksReady.WaitOne();
|
||||||
|
|
||||||
|
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
if (cheevo.IsEnabled && !cheevo.IsUnlocked(false))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_deactivate_achievement(ref _runtime, cheevo.ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameData.HardcoreInitUnlocksReady.WaitOne();
|
||||||
|
|
||||||
|
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
if (cheevo.IsEnabled && !cheevo.IsUnlocked(true))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_activate_achievement(ref _runtime, cheevo.ID, cheevo.Definition, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SendUnlockAchievementAsync(string username, string api_token, int id, bool hardcore, string hash)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_award_achievement_request_t(username, api_token, id, hardcore, hash);
|
||||||
|
var res = LibRCheevos.rc_error_t.RC_INVALID_STATE;
|
||||||
|
if (_lib.rc_api_init_award_achievement_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
res = _lib.rc_api_process_award_achievement_response(out var resp, serv_req);
|
||||||
|
_lib.rc_api_destroy_award_achievement_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
|
||||||
|
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
// todo: warn user? correct local version of cheevos?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void SendUnlockAchievement(string username, string api_token, int id, bool hardcore, string hash)
|
||||||
|
=> await SendUnlockAchievementAsync(username, api_token, id, hardcore, hash).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private readonly RCheevosGameInfoForm _gameInfoForm = new();
|
||||||
|
|
||||||
|
private ConsoleID _consoleId;
|
||||||
|
|
||||||
|
private string _gameHash;
|
||||||
|
private readonly Dictionary<string, int> _cachedGameIds = new(); // keep around IDs per hash to avoid unneeded API calls for a simple RebootCore
|
||||||
|
|
||||||
|
private GameData _gameData;
|
||||||
|
private readonly Dictionary<int, GameData> _cachedGameDatas = new(); // keep game data around to avoid unneeded API calls for a simple RebootCore
|
||||||
|
|
||||||
|
public class GameData
|
||||||
|
{
|
||||||
|
public int GameID { get; }
|
||||||
|
public ConsoleID ConsoleID { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
private string ImageName { get; }
|
||||||
|
public Bitmap GameBadge { get; private set; }
|
||||||
|
public string RichPresenseScript { get; }
|
||||||
|
|
||||||
|
private readonly IReadOnlyDictionary<int, Cheevo> _cheevos;
|
||||||
|
private readonly IReadOnlyDictionary<int, LBoard> _lboards;
|
||||||
|
|
||||||
|
public IEnumerable<Cheevo> CheevoEnumerable => _cheevos.Values;
|
||||||
|
public IEnumerable<LBoard> LBoardEnumerable => _lboards.Values;
|
||||||
|
|
||||||
|
public Cheevo GetCheevoById(int i) => _cheevos[i];
|
||||||
|
public LBoard GetLboardById(int i) => _lboards[i];
|
||||||
|
|
||||||
|
public ManualResetEvent SoftcoreInitUnlocksReady { get; }
|
||||||
|
public ManualResetEvent HardcoreInitUnlocksReady { get; }
|
||||||
|
|
||||||
|
public async Task InitUnlocks(string username, string api_token, bool hardcore)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_fetch_user_unlocks_request_t(username, api_token, GameID, hardcore);
|
||||||
|
if (_lib.rc_api_init_fetch_user_unlocks_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
if (_lib.rc_api_process_fetch_user_unlocks_response(out var resp, serv_req) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var unlocks = (int*)resp.achievement_ids;
|
||||||
|
for (int i = 0; i < resp.num_achievement_ids; i++)
|
||||||
|
{
|
||||||
|
if (_cheevos.TryGetValue(unlocks[i], out var cheevo))
|
||||||
|
{
|
||||||
|
cheevo.SetUnlocked(hardcore, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_fetch_user_unlocks_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
|
||||||
|
if (hardcore)
|
||||||
|
{
|
||||||
|
HardcoreInitUnlocksReady?.Set();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SoftcoreInitUnlocksReady?.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadImages()
|
||||||
|
{
|
||||||
|
GameBadge = await GetImage(ImageName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_GAME).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (_cheevos is null) return;
|
||||||
|
|
||||||
|
foreach (var cheevo in _cheevos.Values)
|
||||||
|
{
|
||||||
|
cheevo.LoadImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TotalCheevoPoints(bool hardcore)
|
||||||
|
=> _cheevos?.Values.Sum(c => (c.IsEnabled && !c.Invalid && c.IsUnlocked(hardcore)) ? c.Points : 0) ?? 0;
|
||||||
|
|
||||||
|
public unsafe GameData(in LibRCheevos.rc_api_fetch_game_data_response_t resp, Func<bool> allowUnofficialCheevos)
|
||||||
|
{
|
||||||
|
GameID = resp.id;
|
||||||
|
ConsoleID = resp.console_id;
|
||||||
|
Title = resp.Title;
|
||||||
|
ImageName = resp.ImageName;
|
||||||
|
GameBadge = null;
|
||||||
|
RichPresenseScript = resp.RichPresenceScript;
|
||||||
|
|
||||||
|
var cheevos = new Dictionary<int, Cheevo>();
|
||||||
|
var cptr = (LibRCheevos.rc_api_achievement_definition_t*)resp.achievements;
|
||||||
|
for (int i = 0; i < resp.num_achievements; i++)
|
||||||
|
{
|
||||||
|
cheevos.Add(cptr[i].id, new(in cptr[i], allowUnofficialCheevos));
|
||||||
|
}
|
||||||
|
|
||||||
|
_cheevos = cheevos;
|
||||||
|
|
||||||
|
var lboards = new Dictionary<int, LBoard>();
|
||||||
|
var lptr = (LibRCheevos.rc_api_leaderboard_definition_t*)resp.leaderboards;
|
||||||
|
for (int i = 0; i < resp.num_leaderboards; i++)
|
||||||
|
{
|
||||||
|
lboards.Add(lptr[i].id, new(in lptr[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
_lboards = lboards;
|
||||||
|
|
||||||
|
SoftcoreInitUnlocksReady = new(false);
|
||||||
|
HardcoreInitUnlocksReady = new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameData(GameData gameData, Func<bool> allowUnofficialCheevos)
|
||||||
|
{
|
||||||
|
GameID = gameData.GameID;
|
||||||
|
ConsoleID = gameData.ConsoleID;
|
||||||
|
Title = gameData.Title;
|
||||||
|
ImageName = gameData.ImageName;
|
||||||
|
GameBadge = null;
|
||||||
|
RichPresenseScript = gameData.RichPresenseScript;
|
||||||
|
|
||||||
|
var cheevos = new Dictionary<int, Cheevo>();
|
||||||
|
foreach (var cheevo in gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
cheevos.Add(cheevo.ID, new(in cheevo, allowUnofficialCheevos));
|
||||||
|
}
|
||||||
|
|
||||||
|
_cheevos = cheevos;
|
||||||
|
|
||||||
|
var lboards = new Dictionary<int, LBoard>();
|
||||||
|
foreach (var lboard in gameData.LBoardEnumerable)
|
||||||
|
{
|
||||||
|
lboards.Add(lboard.ID, new(in lboard));
|
||||||
|
}
|
||||||
|
|
||||||
|
_lboards = lboards;
|
||||||
|
|
||||||
|
SoftcoreInitUnlocksReady = new(false);
|
||||||
|
HardcoreInitUnlocksReady = new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameData()
|
||||||
|
{
|
||||||
|
GameID = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> SendHashAsync(string hash)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_resolve_hash_request_t(null, null, hash);
|
||||||
|
var ret = 0;
|
||||||
|
if (_lib.rc_api_init_resolve_hash_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
if (_lib.rc_api_process_resolve_hash_response(out var resp, serv_req) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
ret = resp.game_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_resolve_hash_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int IdentifyHash(string hash)
|
||||||
|
{
|
||||||
|
_gameHash ??= hash;
|
||||||
|
|
||||||
|
if (_cachedGameIds.ContainsKey(hash))
|
||||||
|
{
|
||||||
|
return _cachedGameIds[hash];
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = SendHashAsync(hash).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
_cachedGameIds.Add(hash, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int IdentifyRom(byte[] rom)
|
||||||
|
{
|
||||||
|
var hash = new byte[33];
|
||||||
|
if (_lib.rc_hash_generate_from_buffer(hash, _consoleId, rom, rom.Length))
|
||||||
|
{
|
||||||
|
return IdentifyHash(Encoding.ASCII.GetString(hash, 0, 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameHash ??= "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task InitGameDataAsync(GameData gameData, string username, string api_token, bool hardcore)
|
||||||
|
{
|
||||||
|
await gameData.InitUnlocks(username, api_token, hardcore).ConfigureAwait(false);
|
||||||
|
await gameData.InitUnlocks(username, api_token, !hardcore).ConfigureAwait(false);
|
||||||
|
await gameData.LoadImages().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void InitGameData(GameData gameData, string username, string api_token, bool hardcore)
|
||||||
|
=> await InitGameDataAsync(gameData, username, api_token, hardcore);
|
||||||
|
|
||||||
|
private static GameData GetGameData(string username, string api_token, int id, Func<bool> allowUnofficialCheevos)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_fetch_game_data_request_t(username, api_token, id);
|
||||||
|
var ret = new GameData();
|
||||||
|
if (_lib.rc_api_init_fetch_game_data_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = SendAPIRequest(in api_req).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
if (_lib.rc_api_process_fetch_game_data_response(out var resp, serv_req) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
ret = new(in resp, allowUnofficialCheevos);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_fetch_game_data_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Bitmap> GetImage(string image_name, LibRCheevos.rc_api_image_type_t image_type)
|
||||||
|
{
|
||||||
|
if (image_name is null) return null;
|
||||||
|
|
||||||
|
var api_params = new LibRCheevos.rc_api_fetch_image_request_t(image_name, image_type);
|
||||||
|
Bitmap ret = null;
|
||||||
|
if (_lib.rc_api_init_fetch_image_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var serv_resp = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
ret = new Bitmap(new MemoryStream(serv_resp));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ret = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private static readonly HttpClient _http = new();
|
||||||
|
|
||||||
|
private static async Task<byte[]> HttpGet(string url)
|
||||||
|
{
|
||||||
|
_http.DefaultRequestHeaders.ConnectionClose = false;
|
||||||
|
var response = await _http.GetAsync(url).ConfigureAwait(false);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<byte[]> HttpPost(string url, string post)
|
||||||
|
{
|
||||||
|
_http.DefaultRequestHeaders.ConnectionClose = true;
|
||||||
|
HttpResponseMessage response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = await _http.PostAsync(url + "?" + post, null).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetBytes(e.ToString()); // bleh
|
||||||
|
}
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<byte[]> SendAPIRequest(in LibRCheevos.rc_api_request_t api_req)
|
||||||
|
=> api_req.post_data != IntPtr.Zero ? HttpPost(api_req.URL, api_req.PostData) : HttpGet(api_req.URL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private readonly RCheevosLeaderboardListForm _lboardListForm = new();
|
||||||
|
|
||||||
|
private bool LBoardsActive { get; set; }
|
||||||
|
|
||||||
|
private LBoard CurrentLboard { get; set; }
|
||||||
|
|
||||||
|
public class LBoard
|
||||||
|
{
|
||||||
|
public int ID { get; }
|
||||||
|
public int Format { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string Definition { get; }
|
||||||
|
public bool LowerIsBetter { get; }
|
||||||
|
public bool Hidden { get; }
|
||||||
|
public bool Invalid { get; set; }
|
||||||
|
public string Score { get; private set; }
|
||||||
|
|
||||||
|
private readonly byte[] _scoreFormatBuffer = new byte[1024];
|
||||||
|
|
||||||
|
public void SetScore(int val)
|
||||||
|
{
|
||||||
|
var len = _lib.rc_runtime_format_lboard_value(_scoreFormatBuffer, _scoreFormatBuffer.Length, val, Format);
|
||||||
|
Score = Encoding.ASCII.GetString(_scoreFormatBuffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LBoard(in LibRCheevos.rc_api_leaderboard_definition_t lboard)
|
||||||
|
{
|
||||||
|
ID = lboard.id;
|
||||||
|
Format = lboard.format;
|
||||||
|
Title = lboard.Title;
|
||||||
|
Description = lboard.Description;
|
||||||
|
Definition = lboard.Definition;
|
||||||
|
LowerIsBetter = lboard.lower_is_better != 0;
|
||||||
|
Hidden = lboard.hidden != 0;
|
||||||
|
Invalid = false;
|
||||||
|
SetScore(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LBoard(in LBoard lboard)
|
||||||
|
{
|
||||||
|
ID = lboard.ID;
|
||||||
|
Format = lboard.Format;
|
||||||
|
Title = lboard.Title;
|
||||||
|
Description = lboard.Description;
|
||||||
|
Definition = lboard.Definition;
|
||||||
|
LowerIsBetter = lboard.LowerIsBetter;
|
||||||
|
Hidden = lboard.Hidden;
|
||||||
|
Invalid = false;
|
||||||
|
SetScore(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SendTriggerLeaderboardAsync(string username, string api_token, int id, int value, string hash)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_submit_lboard_entry_request_t(username, api_token, id, value, hash);
|
||||||
|
var res = LibRCheevos.rc_error_t.RC_INVALID_STATE;
|
||||||
|
if (_lib.rc_api_init_submit_lboard_entry_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
res = _lib.rc_api_process_submit_lboard_entry_response(out var resp, serv_req);
|
||||||
|
_lib.rc_api_destroy_submit_lboard_entry_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
|
||||||
|
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
// todo: warn user?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void SendTriggerLeaderboard(string username, string api_token, int id, int value, string hash)
|
||||||
|
=> await SendTriggerLeaderboardAsync(username, api_token, id, value, hash).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private string Username, ApiToken;
|
||||||
|
private bool LoggedIn => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(ApiToken);
|
||||||
|
|
||||||
|
private ManualResetEvent InitLoginDone { get; }
|
||||||
|
|
||||||
|
private event Action LoginStatusChanged;
|
||||||
|
|
||||||
|
private async Task<bool> LoginCallback(string username, string password)
|
||||||
|
{
|
||||||
|
Username = null;
|
||||||
|
ApiToken = null;
|
||||||
|
|
||||||
|
var api_params = new LibRCheevos.rc_api_login_request_t(username, null, password);
|
||||||
|
if (_lib.rc_api_init_login_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
if (_lib.rc_api_process_login_response(out var resp, serv_req) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
Username = resp.Username;
|
||||||
|
ApiToken = resp.ApiToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_login_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
|
||||||
|
return LoggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Login()
|
||||||
|
{
|
||||||
|
var config = _getConfig();
|
||||||
|
Username = config.RAUsername;
|
||||||
|
ApiToken = config.RAToken;
|
||||||
|
|
||||||
|
if (LoggedIn)
|
||||||
|
{
|
||||||
|
// OK, Username and ApiToken are probably valid, let's ensure they are now
|
||||||
|
var api_params = new LibRCheevos.rc_api_login_request_t(Username, ApiToken, null);
|
||||||
|
|
||||||
|
Username = null;
|
||||||
|
ApiToken = null;
|
||||||
|
|
||||||
|
if (_lib.rc_api_init_login_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
if (_lib.rc_api_process_login_response(out var resp, serv_req) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
Username = resp.Username;
|
||||||
|
ApiToken = resp.ApiToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_login_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoggedIn)
|
||||||
|
{
|
||||||
|
config.RAUsername = Username;
|
||||||
|
config.RAToken = ApiToken;
|
||||||
|
InitLoginDone.Set();
|
||||||
|
if (EnableSoundEffects) _loginSound.Play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var loginForm = new RCheevosLoginForm(LoginCallback);
|
||||||
|
loginForm.ShowDialog();
|
||||||
|
|
||||||
|
config.RAUsername = Username;
|
||||||
|
config.RAToken = ApiToken;
|
||||||
|
InitLoginDone.Set();
|
||||||
|
|
||||||
|
if (LoggedIn && EnableSoundEffects)
|
||||||
|
{
|
||||||
|
_loginSound.Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Logout()
|
||||||
|
{
|
||||||
|
var config = _getConfig();
|
||||||
|
config.RAUsername = Username = string.Empty;
|
||||||
|
config.RAToken = ApiToken = string.Empty;
|
||||||
|
_cachedGameDatas.Clear(); // no longer valid
|
||||||
|
// should be fine to leave other things be, they'll be reinit on login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
private bool RichPresenceActive { get; set; }
|
||||||
|
|
||||||
|
private string CurrentRichPresence { get; set; }
|
||||||
|
|
||||||
|
private static async Task<bool> StartGameSessionAsync(string username, string api_token, int id)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_start_session_request_t(username, api_token, id);
|
||||||
|
var res = LibRCheevos.rc_error_t.RC_INVALID_STATE;
|
||||||
|
if (_lib.rc_api_init_start_session_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
res = _lib.rc_api_process_start_session_response(out var resp, serv_req);
|
||||||
|
_lib.rc_api_destroy_start_session_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
return res == LibRCheevos.rc_error_t.RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: warn on failure?
|
||||||
|
private static async void StartGameSession(string username, string api_token, int id)
|
||||||
|
=> await StartGameSessionAsync(username, api_token, id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
private static async Task SendPingAsync(string username, string api_token, int id, string rich_presence)
|
||||||
|
{
|
||||||
|
var api_params = new LibRCheevos.rc_api_ping_request_t(username, api_token, id, rich_presence);
|
||||||
|
if (_lib.rc_api_init_ping_request(out var api_req, ref api_params) == LibRCheevos.rc_error_t.RC_OK)
|
||||||
|
{
|
||||||
|
var serv_req = await SendAPIRequest(in api_req).ConfigureAwait(false);
|
||||||
|
_lib.rc_api_process_ping_response(out var resp, serv_req);
|
||||||
|
_lib.rc_api_destroy_ping_response(ref resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_api_destroy_request(ref api_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void SendPing(string username, string api_token, int id, string rich_presence)
|
||||||
|
=> await SendPingAsync(username, api_token, id, rich_presence).ConfigureAwait(false);
|
||||||
|
|
||||||
|
private readonly byte[] _richPresenceBuffer = new byte[1024];
|
||||||
|
|
||||||
|
private DateTime _lastPingTime = DateTime.Now;
|
||||||
|
private static readonly TimeSpan _pingCooldown = new(10000000 * 120); // 2 minutes
|
||||||
|
|
||||||
|
private void CheckPing()
|
||||||
|
{
|
||||||
|
if (RichPresenceActive)
|
||||||
|
{
|
||||||
|
var len = _lib.rc_runtime_get_richpresence(ref _runtime, _richPresenceBuffer, _richPresenceBuffer.Length, PeekCallback, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
CurrentRichPresence = Encoding.UTF8.GetString(_richPresenceBuffer, 0, len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentRichPresence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
if ((now - _lastPingTime) >= _pingCooldown)
|
||||||
|
{
|
||||||
|
SendPing(Username, ApiToken, _gameData.GameID, CurrentRichPresence);
|
||||||
|
_lastPingTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Media;
|
||||||
|
|
||||||
|
using BizHawk.Common.PathExtensions;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos
|
||||||
|
{
|
||||||
|
// NOTE: these are net framework only...
|
||||||
|
// this logic should probably be the main sound class
|
||||||
|
// this shouldn't be a blocker to moving to net core anyways
|
||||||
|
private static readonly SoundPlayer _loginSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/login.wav"));
|
||||||
|
private static readonly SoundPlayer _unlockSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/unlock.wav"));
|
||||||
|
private static readonly SoundPlayer _lboardStartSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lb.wav"));
|
||||||
|
private static readonly SoundPlayer _lboardFailedSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lbcancel.wav"));
|
||||||
|
private static readonly SoundPlayer _infoSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/info.wav"));
|
||||||
|
|
||||||
|
private bool EnableSoundEffects { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,636 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using BizHawk.BizInvoke;
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Common.IOExtensions;
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public partial class RCheevos : RetroAchievements
|
||||||
|
{
|
||||||
|
private static readonly LibRCheevos _lib;
|
||||||
|
|
||||||
|
static RCheevos()
|
||||||
|
{
|
||||||
|
var resolver = new DynamicLibraryImportResolver(
|
||||||
|
OSTailoredCode.IsUnixHost ? "librcheevos.so" : "librcheevos.dll", hasLimitedLifetime: false);
|
||||||
|
_lib = BizInvoker.GetInvoker<LibRCheevos>(resolver, CallingConventionAdapters.Native);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LibRCheevos.rc_runtime_t _runtime;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, (ReadMemoryFunc Func, int Start)> _readMap = new();
|
||||||
|
|
||||||
|
private ToolStripMenuItem _hardcoreModeMenuItem;
|
||||||
|
private bool _hardcoreMode;
|
||||||
|
|
||||||
|
private bool HardcoreMode
|
||||||
|
{
|
||||||
|
get => _hardcoreMode;
|
||||||
|
set => _hardcoreModeMenuItem.Checked = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _firstRestart = true;
|
||||||
|
|
||||||
|
private void BuildMenu(ToolStripItemCollection raDropDownItems)
|
||||||
|
{
|
||||||
|
raDropDownItems.Clear();
|
||||||
|
|
||||||
|
var shutDownRAItem = new ToolStripMenuItem("Shutdown RetroAchievements");
|
||||||
|
shutDownRAItem.Click += (_, _) => _shutdownRACallback();
|
||||||
|
raDropDownItems.Add(shutDownRAItem);
|
||||||
|
|
||||||
|
var autoStartRAItem = new ToolStripMenuItem("Autostart RetroAchievements")
|
||||||
|
{
|
||||||
|
Checked = _getConfig().RAAutostart,
|
||||||
|
CheckOnClick = true,
|
||||||
|
};
|
||||||
|
autoStartRAItem.CheckedChanged += (_, _) => _getConfig().RAAutostart ^= true;
|
||||||
|
raDropDownItems.Add(autoStartRAItem);
|
||||||
|
|
||||||
|
var loginItem = new ToolStripMenuItem("Login")
|
||||||
|
{
|
||||||
|
Visible = !LoggedIn
|
||||||
|
};
|
||||||
|
loginItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
Login();
|
||||||
|
_firstRestart = true; // kinda
|
||||||
|
Restart();
|
||||||
|
LoginStatusChanged();
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(loginItem);
|
||||||
|
|
||||||
|
var logoutItem = new ToolStripMenuItem("Logout")
|
||||||
|
{
|
||||||
|
Visible = LoggedIn
|
||||||
|
};
|
||||||
|
logoutItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
Logout();
|
||||||
|
LoginStatusChanged();
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(logoutItem);
|
||||||
|
|
||||||
|
LoginStatusChanged += () => loginItem.Visible = !LoggedIn;
|
||||||
|
LoginStatusChanged += () => logoutItem.Visible = LoggedIn;
|
||||||
|
|
||||||
|
var tss = new ToolStripSeparator();
|
||||||
|
raDropDownItems.Add(tss);
|
||||||
|
|
||||||
|
var enableCheevosItem = new ToolStripMenuItem("Enable Achievements")
|
||||||
|
{
|
||||||
|
Checked = CheevosActive,
|
||||||
|
CheckOnClick = true
|
||||||
|
};
|
||||||
|
enableCheevosItem.CheckedChanged += (_, _) => CheevosActive ^= true;
|
||||||
|
raDropDownItems.Add(enableCheevosItem);
|
||||||
|
|
||||||
|
var enableLboardsItem = new ToolStripMenuItem("Enable Leaderboards")
|
||||||
|
{
|
||||||
|
Checked = LBoardsActive,
|
||||||
|
CheckOnClick = true,
|
||||||
|
Enabled = HardcoreMode
|
||||||
|
};
|
||||||
|
enableLboardsItem.CheckedChanged += (_, _) => LBoardsActive ^= true;
|
||||||
|
raDropDownItems.Add(enableLboardsItem);
|
||||||
|
|
||||||
|
var enableRichPresenceItem = new ToolStripMenuItem("Enable Rich Presence")
|
||||||
|
{
|
||||||
|
Checked = RichPresenceActive,
|
||||||
|
CheckOnClick = true
|
||||||
|
};
|
||||||
|
enableRichPresenceItem.CheckedChanged += (_, _) => RichPresenceActive ^= true;
|
||||||
|
raDropDownItems.Add(enableRichPresenceItem);
|
||||||
|
|
||||||
|
var enableHardcoreItem = new ToolStripMenuItem("Enable Hardcore Mode")
|
||||||
|
{
|
||||||
|
Checked = HardcoreMode,
|
||||||
|
CheckOnClick = true
|
||||||
|
};
|
||||||
|
enableHardcoreItem.CheckedChanged += (_, _) =>
|
||||||
|
{
|
||||||
|
_hardcoreMode ^= true;
|
||||||
|
enableLboardsItem.Enabled = HardcoreMode;
|
||||||
|
|
||||||
|
if (HardcoreMode)
|
||||||
|
{
|
||||||
|
_hardcoreMode = _mainForm.RebootCore(); // unset hardcore mode if we fail to reboot core somehow
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ToSoftcoreMode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(enableHardcoreItem);
|
||||||
|
|
||||||
|
_hardcoreModeMenuItem = enableHardcoreItem;
|
||||||
|
|
||||||
|
var enableSoundEffectsItem = new ToolStripMenuItem("Enable Sound Effects")
|
||||||
|
{
|
||||||
|
Checked = EnableSoundEffects,
|
||||||
|
CheckOnClick = true
|
||||||
|
};
|
||||||
|
enableSoundEffectsItem.CheckedChanged += (_, _) => EnableSoundEffects ^= true;
|
||||||
|
raDropDownItems.Add(enableSoundEffectsItem);
|
||||||
|
|
||||||
|
var enableUnofficialCheevosItem = new ToolStripMenuItem("Test Unofficial Achievements")
|
||||||
|
{
|
||||||
|
Checked = AllowUnofficialCheevos,
|
||||||
|
CheckOnClick = true
|
||||||
|
};
|
||||||
|
enableUnofficialCheevosItem.CheckedChanged += (_, _) => ToggleUnofficialCheevos();
|
||||||
|
raDropDownItems.Add(enableUnofficialCheevosItem);
|
||||||
|
|
||||||
|
tss = new ToolStripSeparator();
|
||||||
|
raDropDownItems.Add(tss);
|
||||||
|
|
||||||
|
var viewGameInfoItem = new ToolStripMenuItem("View Game Info");
|
||||||
|
viewGameInfoItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
_gameInfoForm.OnFrameAdvance(_gameData.GameBadge, _gameData.TotalCheevoPoints(HardcoreMode),
|
||||||
|
CurrentLboard is null ? "N/A" : $"{CurrentLboard.Description} ({CurrentLboard.Score})",
|
||||||
|
CurrentRichPresence ?? "N/A");
|
||||||
|
|
||||||
|
_gameInfoForm.Show();
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(viewGameInfoItem);
|
||||||
|
|
||||||
|
var viewCheevoListItem = new ToolStripMenuItem("View Achievement List");
|
||||||
|
viewCheevoListItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
_cheevoListForm.OnFrameAdvance(HardcoreMode, true);
|
||||||
|
_cheevoListForm.Show();
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(viewCheevoListItem);
|
||||||
|
|
||||||
|
var viewLboardListItem = new ToolStripMenuItem("View Leaderboard List");
|
||||||
|
viewLboardListItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
_lboardListForm.OnFrameAdvance(true);
|
||||||
|
_lboardListForm.Show();
|
||||||
|
};
|
||||||
|
raDropDownItems.Add(viewLboardListItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleHardcoreModeDisable(string reason)
|
||||||
|
{
|
||||||
|
_mainForm.ShowMessageBox(null, $"{reason} Disabling hardcore mode.", "Warning", EMsgBoxIcon.Warning);
|
||||||
|
HardcoreMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RCheevos(IMainFormForRetroAchievements mainForm, InputManager inputManager, ToolManager tools,
|
||||||
|
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
|
||||||
|
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
|
||||||
|
{
|
||||||
|
_runtime = default;
|
||||||
|
_lib.rc_runtime_init(ref _runtime);
|
||||||
|
InitLoginDone = new(false);
|
||||||
|
Login();
|
||||||
|
InitLoginDone.WaitOne();
|
||||||
|
|
||||||
|
var config = _getConfig();
|
||||||
|
CheevosActive = config.RACheevosActive;
|
||||||
|
LBoardsActive = config.RALBoardsActive;
|
||||||
|
RichPresenceActive = config.RARichPresenceActive;
|
||||||
|
_hardcoreMode = config.RAHardcoreMode;
|
||||||
|
EnableSoundEffects = config.RASoundEffects;
|
||||||
|
AllowUnofficialCheevos = config.RAAllowUnofficialCheevos;
|
||||||
|
|
||||||
|
BuildMenu(raDropDownItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_destroy(ref _runtime);
|
||||||
|
Stop();
|
||||||
|
_gameInfoForm.Dispose();
|
||||||
|
_cheevoListForm.Dispose();
|
||||||
|
_lboardListForm.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSaveState(string path)
|
||||||
|
{
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = _lib.rc_runtime_progress_size(ref _runtime, IntPtr.Zero);
|
||||||
|
if (size > 0)
|
||||||
|
{
|
||||||
|
var buffer = new byte[(int)size];
|
||||||
|
_lib.rc_runtime_serialize_progress(buffer, ref _runtime, IntPtr.Zero);
|
||||||
|
using var file = File.OpenWrite(path + ".rap");
|
||||||
|
file.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnLoadState(string path)
|
||||||
|
{
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HardcoreMode)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_lib.rc_runtime_reset(ref _runtime);
|
||||||
|
|
||||||
|
if (File.Exists(path + ".rap"))
|
||||||
|
{
|
||||||
|
using var file = File.OpenRead(path + ".rap");
|
||||||
|
var buffer = file.ReadAllBytes();
|
||||||
|
_lib.rc_runtime_deserialize_progress(ref _runtime, buffer, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not sure if we really need to do anything here...
|
||||||
|
// nice way to ensure config is written back every so often (and on close)
|
||||||
|
public override void Stop()
|
||||||
|
{
|
||||||
|
var config = _getConfig();
|
||||||
|
config.RACheevosActive = CheevosActive;
|
||||||
|
config.RALBoardsActive = LBoardsActive;
|
||||||
|
config.RARichPresenceActive = RichPresenceActive;
|
||||||
|
config.RAHardcoreMode = HardcoreMode;
|
||||||
|
config.RASoundEffects = EnableSoundEffects;
|
||||||
|
config.RAAllowUnofficialCheevos = AllowUnofficialCheevos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Restart()
|
||||||
|
{
|
||||||
|
if (_firstRestart)
|
||||||
|
{
|
||||||
|
_firstRestart = false;
|
||||||
|
if (HardcoreMode)
|
||||||
|
{
|
||||||
|
HardcoreMode = _mainForm.RebootCore(); // unset hardcore mode if we fail to reboot core somehow
|
||||||
|
if (HardcoreMode && _mainForm.CurrentlyOpenRomArgs is not null)
|
||||||
|
{
|
||||||
|
// if we aren't hardcore anymore, we failed to reboot the core (and didn't call Restart probably)
|
||||||
|
// if CurrentlyOpenRomArgs is null, then Restart won't be called (as RebootCore returns true immediately), so
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reinit the runtime
|
||||||
|
_lib.rc_runtime_destroy(ref _runtime);
|
||||||
|
_runtime = default;
|
||||||
|
_lib.rc_runtime_init(ref _runtime);
|
||||||
|
|
||||||
|
// get console id
|
||||||
|
_consoleId = SystemIdToConsoleId();
|
||||||
|
|
||||||
|
// init the read map
|
||||||
|
_readMap.Clear();
|
||||||
|
|
||||||
|
if (Emu.HasMemoryDomains())
|
||||||
|
{
|
||||||
|
_memFunctions = CreateMemoryBanks(_consoleId, Domains, Emu.CanDebug() ? Emu.AsDebuggable() : null);
|
||||||
|
|
||||||
|
var addr = 0;
|
||||||
|
foreach (var memFunctions in _memFunctions)
|
||||||
|
{
|
||||||
|
if (memFunctions.ReadFunc is not null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < memFunctions.BankSize; i++)
|
||||||
|
{
|
||||||
|
_readMap.Add(addr + i, (memFunctions.ReadFunc, addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr += memFunctions.BankSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify and init whatever is loaded
|
||||||
|
AllGamesVerified = true;
|
||||||
|
_gameHash = null; // will be set by first IdentifyHash
|
||||||
|
|
||||||
|
if (_mainForm.CurrentlyOpenRomArgs is not null)
|
||||||
|
{
|
||||||
|
var ids = GetRAGameIds(_mainForm.CurrentlyOpenRomArgs.OpenAdvanced, _consoleId);
|
||||||
|
|
||||||
|
AllGamesVerified = !ids.Contains(0);
|
||||||
|
|
||||||
|
var gameId = ids.Count > 0 ? ids[0] : 0;
|
||||||
|
|
||||||
|
if (gameId != 0)
|
||||||
|
{
|
||||||
|
if (_cachedGameDatas.TryGetValue(gameId, out var cachedGameData))
|
||||||
|
{
|
||||||
|
_gameData = new GameData(cachedGameData, () => AllowUnofficialCheevos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameData = GetGameData(Username, ApiToken, gameId, () => AllowUnofficialCheevos);
|
||||||
|
}
|
||||||
|
|
||||||
|
StartGameSession(Username, ApiToken, gameId);
|
||||||
|
|
||||||
|
_cachedGameDatas.Remove(gameId);
|
||||||
|
_cachedGameDatas.Add(gameId, _gameData);
|
||||||
|
|
||||||
|
InitGameData(_gameData, Username, ApiToken, HardcoreMode);
|
||||||
|
|
||||||
|
foreach (var lboard in _gameData.LBoardEnumerable)
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_activate_lboard(ref _runtime, lboard.ID, lboard.Definition, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameData.RichPresenseScript is not null)
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_activate_richpresence(ref _runtime, _gameData.RichPresenseScript, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var waitInit = HardcoreMode ? _gameData.HardcoreInitUnlocksReady : _gameData.SoftcoreInitUnlocksReady;
|
||||||
|
// hopefully not too long, given we spent some time doing other work
|
||||||
|
waitInit.WaitOne();
|
||||||
|
|
||||||
|
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||||
|
{
|
||||||
|
if (cheevo.IsEnabled && !cheevo.IsUnlocked(HardcoreMode))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_activate_achievement(ref _runtime, cheevo.ID, cheevo.Definition, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameData = new GameData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameData = new GameData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate addresses now that we have cheevos init
|
||||||
|
_lib.rc_runtime_validate_addresses(ref _runtime, EventHandlerCallback, address => _readMap.ContainsKey(address));
|
||||||
|
|
||||||
|
_gameInfoForm.Restart(_gameData.Title, _gameData.TotalCheevoPoints(HardcoreMode), CurrentRichPresence ?? "N/A");
|
||||||
|
_cheevoListForm.Restart(_gameData.GameID == 0 ? Array.Empty<Cheevo>() : _gameData.CheevoEnumerable, GetCheevoProgress);
|
||||||
|
_lboardListForm.Restart(_gameData.GameID == 0 ? Array.Empty<LBoard>() : _gameData.LBoardEnumerable);
|
||||||
|
|
||||||
|
Update();
|
||||||
|
|
||||||
|
// note: this can only catch quicksaves (probably only case of accidential use from hotkeys)
|
||||||
|
_mainForm.EmuClient.BeforeQuickLoad += (_, e) =>
|
||||||
|
{
|
||||||
|
if (HardcoreMode)
|
||||||
|
{
|
||||||
|
e.Handled = _mainForm.ShowMessageBox2(null, "Loading a quicksave is not allowed in hardcode mode. Abort loading state?", "Warning", EMsgBoxIcon.Warning);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HardcoreMode)
|
||||||
|
{
|
||||||
|
CheckHardcoreModeConditions();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameData.GameID != 0)
|
||||||
|
{
|
||||||
|
CheckPing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void EventHandlerCallback(IntPtr runtime_event)
|
||||||
|
{
|
||||||
|
var evt = (LibRCheevos.rc_runtime_event_t*)runtime_event;
|
||||||
|
switch (evt->type)
|
||||||
|
{
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED:
|
||||||
|
{
|
||||||
|
if (!CheevosActive) return;
|
||||||
|
|
||||||
|
var cheevo = _gameData.GetCheevoById(evt->id);
|
||||||
|
if (cheevo.IsEnabled)
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_deactivate_achievement(ref _runtime, evt->id);
|
||||||
|
|
||||||
|
cheevo.SetUnlocked(HardcoreMode, true);
|
||||||
|
var prefix = HardcoreMode ? "[HARDCORE] " : "";
|
||||||
|
_mainForm.AddOnScreenMessage($"{prefix}Achievement Unlocked!");
|
||||||
|
_mainForm.AddOnScreenMessage(cheevo.Description);
|
||||||
|
if (EnableSoundEffects) _unlockSound.Play();
|
||||||
|
|
||||||
|
if (cheevo.IsOfficial)
|
||||||
|
{
|
||||||
|
SendUnlockAchievement(Username, ApiToken, evt->id, HardcoreMode, _gameHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED:
|
||||||
|
{
|
||||||
|
if (!CheevosActive) return;
|
||||||
|
|
||||||
|
var cheevo = _gameData.GetCheevoById(evt->id);
|
||||||
|
if (cheevo.IsEnabled)
|
||||||
|
{
|
||||||
|
cheevo.IsPrimed = true;
|
||||||
|
var prefix = HardcoreMode ? "[HARDCORE] " : "";
|
||||||
|
_mainForm.AddOnScreenMessage($"{prefix}Achievement Primed!");
|
||||||
|
_mainForm.AddOnScreenMessage(cheevo.Description);
|
||||||
|
if (EnableSoundEffects) _infoSound.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_LBOARD_STARTED:
|
||||||
|
{
|
||||||
|
if (!LBoardsActive || !HardcoreMode) return;
|
||||||
|
|
||||||
|
var lboard = _gameData.GetLboardById(evt->id);
|
||||||
|
if (!lboard.Invalid)
|
||||||
|
{
|
||||||
|
lboard.SetScore(evt->value);
|
||||||
|
|
||||||
|
if (!lboard.Hidden)
|
||||||
|
{
|
||||||
|
CurrentLboard = lboard;
|
||||||
|
_mainForm.AddOnScreenMessage($"Leaderboard Attempt Started!");
|
||||||
|
_mainForm.AddOnScreenMessage(lboard.Description);
|
||||||
|
if (EnableSoundEffects) _lboardStartSound.Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_LBOARD_CANCELED:
|
||||||
|
{
|
||||||
|
if (!LBoardsActive || !HardcoreMode) return;
|
||||||
|
|
||||||
|
var lboard = _gameData.GetLboardById(evt->id);
|
||||||
|
if (!lboard.Invalid)
|
||||||
|
{
|
||||||
|
if (!lboard.Hidden)
|
||||||
|
{
|
||||||
|
if (lboard == CurrentLboard)
|
||||||
|
{
|
||||||
|
CurrentLboard = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mainForm.AddOnScreenMessage($"Leaderboard Attempt Failed! ({lboard.Score})");
|
||||||
|
_mainForm.AddOnScreenMessage(lboard.Description);
|
||||||
|
if (EnableSoundEffects) _lboardFailedSound.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
lboard.SetScore(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_LBOARD_UPDATED:
|
||||||
|
{
|
||||||
|
if (!LBoardsActive || !HardcoreMode) return;
|
||||||
|
|
||||||
|
var lboard = _gameData.GetLboardById(evt->id);
|
||||||
|
if (!lboard.Invalid)
|
||||||
|
{
|
||||||
|
lboard.SetScore(evt->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_LBOARD_TRIGGERED:
|
||||||
|
{
|
||||||
|
if (!LBoardsActive || !HardcoreMode) return;
|
||||||
|
|
||||||
|
var lboard = _gameData.GetLboardById(evt->id);
|
||||||
|
if (!lboard.Invalid)
|
||||||
|
{
|
||||||
|
SendTriggerLeaderboard(Username, ApiToken, evt->id, evt->value, _gameHash);
|
||||||
|
|
||||||
|
if (!lboard.Hidden)
|
||||||
|
{
|
||||||
|
if (lboard == CurrentLboard)
|
||||||
|
{
|
||||||
|
CurrentLboard = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mainForm.AddOnScreenMessage($"Leaderboard Attempt Complete! ({lboard.Score})");
|
||||||
|
_mainForm.AddOnScreenMessage(lboard.Description);
|
||||||
|
if (EnableSoundEffects) _unlockSound.Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED:
|
||||||
|
{
|
||||||
|
var cheevo = _gameData.GetCheevoById(evt->id);
|
||||||
|
cheevo.Invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_LBOARD_DISABLED:
|
||||||
|
{
|
||||||
|
var lboard = _gameData.GetLboardById(evt->id);
|
||||||
|
lboard.Invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LibRCheevos.rc_runtime_event_type_t.RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED:
|
||||||
|
{
|
||||||
|
var cheevo = _gameData.GetCheevoById(evt->id);
|
||||||
|
if (cheevo.IsEnabled)
|
||||||
|
{
|
||||||
|
cheevo.IsPrimed = false;
|
||||||
|
var prefix = HardcoreMode ? "[HARDCORE] " : "";
|
||||||
|
_mainForm.AddOnScreenMessage($"{prefix}Achievement Unprimed!");
|
||||||
|
_mainForm.AddOnScreenMessage(cheevo.Description);
|
||||||
|
if (EnableSoundEffects) _infoSound.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int PeekCallback(int address, int num_bytes, IntPtr ud)
|
||||||
|
{
|
||||||
|
byte Peek(int addr)
|
||||||
|
=> _readMap.TryGetValue(addr, out var reader) ? reader.Func(addr - reader.Start) : (byte)0;
|
||||||
|
|
||||||
|
return num_bytes switch
|
||||||
|
{
|
||||||
|
1 => Peek(address),
|
||||||
|
2 => Peek(address) | (Peek(address + 1) << 8),
|
||||||
|
4 => Peek(address) | (Peek(address + 1) << 8) | (Peek(address + 2) << 16) | (Peek(address + 3) << 24),
|
||||||
|
_ => throw new InvalidOperationException($"Requested {num_bytes} in {nameof(PeekCallback)}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameAdvance()
|
||||||
|
{
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = _inputManager.ControllerOutput;
|
||||||
|
foreach (var resetButton in input.Definition.BoolButtons.Where(b => b.Contains("Power") || b.Contains("Reset")))
|
||||||
|
{
|
||||||
|
if (input.IsPressed(resetButton))
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_reset(ref _runtime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Emu.HasMemoryDomains())
|
||||||
|
{
|
||||||
|
// we want to EnterExit to prevent wbx host spam when peeks are spammed
|
||||||
|
using (Domains.MainMemory.EnterExit())
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_do_frame(ref _runtime, EventHandlerCallback, PeekCallback, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_lib.rc_runtime_do_frame(ref _runtime, EventHandlerCallback, PeekCallback, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameInfoForm.IsShown)
|
||||||
|
{
|
||||||
|
_gameInfoForm.OnFrameAdvance(_gameData.GameBadge, _gameData.TotalCheevoPoints(HardcoreMode),
|
||||||
|
CurrentLboard is null ? "N/A" : $"{CurrentLboard.Description} ({CurrentLboard.Score})",
|
||||||
|
CurrentRichPresence ?? "N/A");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cheevoListForm.IsShown)
|
||||||
|
{
|
||||||
|
_cheevoListForm.OnFrameAdvance(HardcoreMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lboardListForm.IsShown)
|
||||||
|
{
|
||||||
|
_lboardListForm.OnFrameAdvance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
224
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.Designer.cs
generated
Normal file
224
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosAchievementForm
|
||||||
|
{
|
||||||
|
/// <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.cheevoBadgeBox = new System.Windows.Forms.PictureBox();
|
||||||
|
this.titleLabel = new System.Windows.Forms.Label();
|
||||||
|
this.descriptionLabel = new System.Windows.Forms.Label();
|
||||||
|
this.titleBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.descriptionBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.pointsLabel = new System.Windows.Forms.Label();
|
||||||
|
this.pointsBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.progressBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.progressLabel = new System.Windows.Forms.Label();
|
||||||
|
this.unofficialCheckBox = new System.Windows.Forms.CheckBox();
|
||||||
|
this.hcUnlockedCheckBox = new System.Windows.Forms.CheckBox();
|
||||||
|
this.primedCheckBox = new System.Windows.Forms.CheckBox();
|
||||||
|
this.scUnlockedCheckBox = new System.Windows.Forms.CheckBox();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.cheevoBadgeBox)).BeginInit();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// cheevoBadgeBox
|
||||||
|
//
|
||||||
|
this.cheevoBadgeBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.cheevoBadgeBox.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.cheevoBadgeBox.Name = "cheevoBadgeBox";
|
||||||
|
this.cheevoBadgeBox.Size = new System.Drawing.Size(120, 120);
|
||||||
|
this.cheevoBadgeBox.TabIndex = 0;
|
||||||
|
this.cheevoBadgeBox.TabStop = false;
|
||||||
|
//
|
||||||
|
// titleLabel
|
||||||
|
//
|
||||||
|
this.titleLabel.AutoSize = true;
|
||||||
|
this.titleLabel.Location = new System.Drawing.Point(171, 15);
|
||||||
|
this.titleLabel.Name = "titleLabel";
|
||||||
|
this.titleLabel.Size = new System.Drawing.Size(30, 13);
|
||||||
|
this.titleLabel.TabIndex = 1;
|
||||||
|
this.titleLabel.Text = "Title:";
|
||||||
|
//
|
||||||
|
// descriptionLabel
|
||||||
|
//
|
||||||
|
this.descriptionLabel.AutoSize = true;
|
||||||
|
this.descriptionLabel.Location = new System.Drawing.Point(138, 40);
|
||||||
|
this.descriptionLabel.Name = "descriptionLabel";
|
||||||
|
this.descriptionLabel.Size = new System.Drawing.Size(63, 13);
|
||||||
|
this.descriptionLabel.TabIndex = 2;
|
||||||
|
this.descriptionLabel.Text = "Description:";
|
||||||
|
//
|
||||||
|
// titleBox
|
||||||
|
//
|
||||||
|
this.titleBox.Location = new System.Drawing.Point(207, 12);
|
||||||
|
this.titleBox.Name = "titleBox";
|
||||||
|
this.titleBox.ReadOnly = true;
|
||||||
|
this.titleBox.Size = new System.Drawing.Size(285, 20);
|
||||||
|
this.titleBox.TabIndex = 3;
|
||||||
|
//
|
||||||
|
// descriptionBox
|
||||||
|
//
|
||||||
|
this.descriptionBox.Location = new System.Drawing.Point(207, 37);
|
||||||
|
this.descriptionBox.Name = "descriptionBox";
|
||||||
|
this.descriptionBox.ReadOnly = true;
|
||||||
|
this.descriptionBox.Size = new System.Drawing.Size(285, 20);
|
||||||
|
this.descriptionBox.TabIndex = 4;
|
||||||
|
//
|
||||||
|
// pointsLabel
|
||||||
|
//
|
||||||
|
this.pointsLabel.AutoSize = true;
|
||||||
|
this.pointsLabel.Location = new System.Drawing.Point(162, 66);
|
||||||
|
this.pointsLabel.Name = "pointsLabel";
|
||||||
|
this.pointsLabel.Size = new System.Drawing.Size(39, 13);
|
||||||
|
this.pointsLabel.TabIndex = 5;
|
||||||
|
this.pointsLabel.Text = "Points:";
|
||||||
|
//
|
||||||
|
// pointsBox
|
||||||
|
//
|
||||||
|
this.pointsBox.Location = new System.Drawing.Point(207, 63);
|
||||||
|
this.pointsBox.Name = "pointsBox";
|
||||||
|
this.pointsBox.ReadOnly = true;
|
||||||
|
this.pointsBox.Size = new System.Drawing.Size(285, 20);
|
||||||
|
this.pointsBox.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// progressBox
|
||||||
|
//
|
||||||
|
this.progressBox.Location = new System.Drawing.Point(207, 89);
|
||||||
|
this.progressBox.Name = "progressBox";
|
||||||
|
this.progressBox.ReadOnly = true;
|
||||||
|
this.progressBox.Size = new System.Drawing.Size(285, 20);
|
||||||
|
this.progressBox.TabIndex = 7;
|
||||||
|
//
|
||||||
|
// progressLabel
|
||||||
|
//
|
||||||
|
this.progressLabel.AutoSize = true;
|
||||||
|
this.progressLabel.Location = new System.Drawing.Point(150, 92);
|
||||||
|
this.progressLabel.Name = "progressLabel";
|
||||||
|
this.progressLabel.Size = new System.Drawing.Size(51, 13);
|
||||||
|
this.progressLabel.TabIndex = 8;
|
||||||
|
this.progressLabel.Text = "Progress:";
|
||||||
|
//
|
||||||
|
// unofficialCheckBox
|
||||||
|
//
|
||||||
|
this.unofficialCheckBox.AutoCheck = false;
|
||||||
|
this.unofficialCheckBox.AutoSize = true;
|
||||||
|
this.unofficialCheckBox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
this.unofficialCheckBox.Location = new System.Drawing.Point(419, 115);
|
||||||
|
this.unofficialCheckBox.Name = "unofficialCheckBox";
|
||||||
|
this.unofficialCheckBox.RightToLeft = System.Windows.Forms.RightToLeft.No;
|
||||||
|
this.unofficialCheckBox.Size = new System.Drawing.Size(73, 17);
|
||||||
|
this.unofficialCheckBox.TabIndex = 9;
|
||||||
|
this.unofficialCheckBox.Text = "Unofficial:";
|
||||||
|
this.unofficialCheckBox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// hcUnlockedCheckBox
|
||||||
|
//
|
||||||
|
this.hcUnlockedCheckBox.AutoCheck = false;
|
||||||
|
this.hcUnlockedCheckBox.AutoSize = true;
|
||||||
|
this.hcUnlockedCheckBox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
this.hcUnlockedCheckBox.Location = new System.Drawing.Point(247, 115);
|
||||||
|
this.hcUnlockedCheckBox.Name = "hcUnlockedCheckBox";
|
||||||
|
this.hcUnlockedCheckBox.RightToLeft = System.Windows.Forms.RightToLeft.No;
|
||||||
|
this.hcUnlockedCheckBox.Size = new System.Drawing.Size(99, 17);
|
||||||
|
this.hcUnlockedCheckBox.TabIndex = 10;
|
||||||
|
this.hcUnlockedCheckBox.Text = "(HC) Unlocked:";
|
||||||
|
this.hcUnlockedCheckBox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// primedCheckBox
|
||||||
|
//
|
||||||
|
this.primedCheckBox.AutoCheck = false;
|
||||||
|
this.primedCheckBox.AutoSize = true;
|
||||||
|
this.primedCheckBox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
this.primedCheckBox.Location = new System.Drawing.Point(352, 115);
|
||||||
|
this.primedCheckBox.Name = "primedCheckBox";
|
||||||
|
this.primedCheckBox.RightToLeft = System.Windows.Forms.RightToLeft.No;
|
||||||
|
this.primedCheckBox.Size = new System.Drawing.Size(61, 17);
|
||||||
|
this.primedCheckBox.TabIndex = 11;
|
||||||
|
this.primedCheckBox.Text = "Primed:";
|
||||||
|
this.primedCheckBox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// scUnlockedCheckBox
|
||||||
|
//
|
||||||
|
this.scUnlockedCheckBox.AutoCheck = false;
|
||||||
|
this.scUnlockedCheckBox.AutoSize = true;
|
||||||
|
this.scUnlockedCheckBox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
this.scUnlockedCheckBox.Location = new System.Drawing.Point(143, 115);
|
||||||
|
this.scUnlockedCheckBox.Name = "scUnlockedCheckBox";
|
||||||
|
this.scUnlockedCheckBox.RightToLeft = System.Windows.Forms.RightToLeft.No;
|
||||||
|
this.scUnlockedCheckBox.Size = new System.Drawing.Size(98, 17);
|
||||||
|
this.scUnlockedCheckBox.TabIndex = 12;
|
||||||
|
this.scUnlockedCheckBox.Text = "(SC) Unlocked:";
|
||||||
|
this.scUnlockedCheckBox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// RCheevosAchievementForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(504, 120);
|
||||||
|
this.ControlBox = false;
|
||||||
|
this.Controls.Add(this.scUnlockedCheckBox);
|
||||||
|
this.Controls.Add(this.primedCheckBox);
|
||||||
|
this.Controls.Add(this.hcUnlockedCheckBox);
|
||||||
|
this.Controls.Add(this.unofficialCheckBox);
|
||||||
|
this.Controls.Add(this.progressLabel);
|
||||||
|
this.Controls.Add(this.progressBox);
|
||||||
|
this.Controls.Add(this.pointsBox);
|
||||||
|
this.Controls.Add(this.pointsLabel);
|
||||||
|
this.Controls.Add(this.descriptionBox);
|
||||||
|
this.Controls.Add(this.titleBox);
|
||||||
|
this.Controls.Add(this.descriptionLabel);
|
||||||
|
this.Controls.Add(this.titleLabel);
|
||||||
|
this.Controls.Add(this.cheevoBadgeBox);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(300, 120);
|
||||||
|
this.Name = "RCheevosAchievementForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.cheevoBadgeBox)).EndInit();
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.PictureBox cheevoBadgeBox;
|
||||||
|
private System.Windows.Forms.Label titleLabel;
|
||||||
|
private System.Windows.Forms.Label descriptionLabel;
|
||||||
|
private System.Windows.Forms.TextBox titleBox;
|
||||||
|
private System.Windows.Forms.TextBox descriptionBox;
|
||||||
|
private System.Windows.Forms.Label pointsLabel;
|
||||||
|
private System.Windows.Forms.TextBox pointsBox;
|
||||||
|
private System.Windows.Forms.TextBox progressBox;
|
||||||
|
private System.Windows.Forms.Label progressLabel;
|
||||||
|
private System.Windows.Forms.CheckBox unofficialCheckBox;
|
||||||
|
private System.Windows.Forms.CheckBox hcUnlockedCheckBox;
|
||||||
|
private System.Windows.Forms.CheckBox primedCheckBox;
|
||||||
|
private System.Windows.Forms.CheckBox scUnlockedCheckBox;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows information about a specific achievement
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosAchievementForm : Form
|
||||||
|
{
|
||||||
|
public int OrderByKey()
|
||||||
|
{
|
||||||
|
var ret = 0;
|
||||||
|
ret += hcUnlockedCheckBox.Checked ? 3 : 0;
|
||||||
|
ret += scUnlockedCheckBox.Checked ? 2 : 0;
|
||||||
|
ret += primedCheckBox.Checked ? 1 : 0;
|
||||||
|
ret += string.IsNullOrEmpty(progressBox.Text) ? 0 : 1;
|
||||||
|
ret += unofficialCheckBox.Checked ? -10 : 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap _unlockedBadge, _lockedBadge;
|
||||||
|
private readonly RCheevos.Cheevo _cheevo;
|
||||||
|
private readonly Func<int, string> _getCheevoProgress;
|
||||||
|
|
||||||
|
public RCheevosAchievementForm(RCheevos.Cheevo cheevo, Func<int, string> getCheevoProgress)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
titleBox.Text = cheevo.Title;
|
||||||
|
descriptionBox.Text = cheevo.Description;
|
||||||
|
pointsBox.Text = cheevo.Points.ToString();
|
||||||
|
progressBox.Text = getCheevoProgress(cheevo.ID);
|
||||||
|
unofficialCheckBox.Checked = !cheevo.IsOfficial;
|
||||||
|
hcUnlockedCheckBox.Checked = cheevo.IsHardcoreUnlocked;
|
||||||
|
primedCheckBox.Checked = cheevo.IsPrimed;
|
||||||
|
scUnlockedCheckBox.Checked = cheevo.IsSoftcoreUnlocked;
|
||||||
|
_cheevo = cheevo;
|
||||||
|
_getCheevoProgress = getCheevoProgress;
|
||||||
|
TopLevel = false;
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap UpscaleBadge(Bitmap src)
|
||||||
|
{
|
||||||
|
var ret = new Bitmap(120, 120);
|
||||||
|
using var g = Graphics.FromImage(ret);
|
||||||
|
g.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||||
|
g.PixelOffsetMode = PixelOffsetMode.Half;
|
||||||
|
g.DrawImage(src, 0, 0, 120, 120);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameAdvance(bool hardcore)
|
||||||
|
{
|
||||||
|
var unlockedBadge = _cheevo.BadgeUnlocked;
|
||||||
|
if (_unlockedBadge is null && unlockedBadge is not null)
|
||||||
|
{
|
||||||
|
_unlockedBadge = UpscaleBadge(unlockedBadge);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lockedBadge = _cheevo.BadgeLocked;
|
||||||
|
if (_lockedBadge is null && lockedBadge is not null)
|
||||||
|
{
|
||||||
|
_lockedBadge = UpscaleBadge(lockedBadge);
|
||||||
|
}
|
||||||
|
|
||||||
|
var badge = _cheevo.IsUnlocked(hardcore) ? _unlockedBadge : _lockedBadge;
|
||||||
|
|
||||||
|
if (cheevoBadgeBox.Image != badge)
|
||||||
|
{
|
||||||
|
cheevoBadgeBox.Image = badge;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointsBox.Text = _cheevo.Points.ToString();
|
||||||
|
progressBox.Text = _getCheevoProgress(_cheevo.ID);
|
||||||
|
hcUnlockedCheckBox.Checked = _cheevo.IsHardcoreUnlocked;
|
||||||
|
primedCheckBox.Checked = _cheevo.IsPrimed;
|
||||||
|
scUnlockedCheckBox.Checked = _cheevo.IsSoftcoreUnlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
68
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs
generated
Normal file
68
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosAchievementListForm
|
||||||
|
{
|
||||||
|
/// <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.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.flowLayoutPanel1.AutoScroll = true;
|
||||||
|
this.flowLayoutPanel1.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.flowLayoutPanel1.MinimumSize = new System.Drawing.Size(544, 0);
|
||||||
|
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
this.flowLayoutPanel1.Size = new System.Drawing.Size(544, 567);
|
||||||
|
this.flowLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// RCheevosAchievementListForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
|
||||||
|
this.ClientSize = new System.Drawing.Size(568, 591);
|
||||||
|
this.Controls.Add(this.flowLayoutPanel1);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(574, 244);
|
||||||
|
this.Name = "RCheevosAchievementListForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Achievement List";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a list of a user's current achievements
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosAchievementListForm : Form
|
||||||
|
{
|
||||||
|
public bool IsShown { get; private set; }
|
||||||
|
|
||||||
|
private RCheevosAchievementForm[] _cheevoForms;
|
||||||
|
private int _updateCooldown;
|
||||||
|
|
||||||
|
public RCheevosAchievementListForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
FormClosing += RCheevosAchievementListForm_FormClosing;
|
||||||
|
Shown += (_, _) => IsShown = true;
|
||||||
|
_cheevoForms = Array.Empty<RCheevosAchievementForm>();
|
||||||
|
_updateCooldown = 5; // only update every 5 frames / 12 fps (as this is rather expensive to update)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeCheevoForms()
|
||||||
|
{
|
||||||
|
foreach (var cheevoForm in _cheevoForms)
|
||||||
|
{
|
||||||
|
cheevoForm.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Restart(IEnumerable<RCheevos.Cheevo> cheevos, Func<int, string> getCheevoProgress)
|
||||||
|
{
|
||||||
|
flowLayoutPanel1.Controls.Clear();
|
||||||
|
DisposeCheevoForms();
|
||||||
|
var cheevoForms = new List<RCheevosAchievementForm>();
|
||||||
|
foreach (var cheevo in cheevos)
|
||||||
|
{
|
||||||
|
cheevoForms.Add(new(cheevo, getCheevoProgress));
|
||||||
|
}
|
||||||
|
_cheevoForms = cheevoForms.OrderByDescending(f => f.OrderByKey()).ToArray();
|
||||||
|
flowLayoutPanel1.Controls.AddRange(_cheevoForms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameAdvance(bool hardcore, bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
_updateCooldown--;
|
||||||
|
if (_updateCooldown == 0 || forceUpdate)
|
||||||
|
{
|
||||||
|
_updateCooldown = 5;
|
||||||
|
|
||||||
|
for (int i = 0; i < _cheevoForms.Length; i++)
|
||||||
|
{
|
||||||
|
_cheevoForms[i].OnFrameAdvance(hardcore);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reorderedForms = _cheevoForms.OrderByDescending(f => f.OrderByKey()).ToArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < _cheevoForms.Length; i++)
|
||||||
|
{
|
||||||
|
if (_cheevoForms[i] != reorderedForms[i])
|
||||||
|
{
|
||||||
|
flowLayoutPanel1.Controls.SetChildIndex(reorderedForms[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cheevoForms = reorderedForms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RCheevosAchievementListForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
e.Cancel = true;
|
||||||
|
IsShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
178
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosGameInfoForm.Designer.cs
generated
Normal file
178
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosGameInfoForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosGameInfoForm
|
||||||
|
{
|
||||||
|
/// <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.gameIconBox = new System.Windows.Forms.PictureBox();
|
||||||
|
this.label2 = new System.Windows.Forms.Label();
|
||||||
|
this.label1 = new System.Windows.Forms.Label();
|
||||||
|
this.label3 = new System.Windows.Forms.Label();
|
||||||
|
this.label5 = new System.Windows.Forms.Label();
|
||||||
|
this.titleTextBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.totalPointsBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.currentLboardBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.richPresenceBox = new System.Windows.Forms.TextBox();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.gameIconBox)).BeginInit();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// gameIconBox
|
||||||
|
//
|
||||||
|
this.gameIconBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.gameIconBox.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.gameIconBox.Name = "gameIconBox";
|
||||||
|
this.gameIconBox.Size = new System.Drawing.Size(100, 100);
|
||||||
|
this.gameIconBox.TabIndex = 0;
|
||||||
|
this.gameIconBox.TabStop = false;
|
||||||
|
//
|
||||||
|
// label2
|
||||||
|
//
|
||||||
|
this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.label2.AutoSize = true;
|
||||||
|
this.label2.Location = new System.Drawing.Point(118, 68);
|
||||||
|
this.label2.Name = "label2";
|
||||||
|
this.label2.Size = new System.Drawing.Size(107, 13);
|
||||||
|
this.label2.TabIndex = 2;
|
||||||
|
this.label2.Text = "Current Leaderboard:";
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
this.label1.AutoSize = true;
|
||||||
|
this.label1.Location = new System.Drawing.Point(195, 15);
|
||||||
|
this.label1.Name = "label1";
|
||||||
|
this.label1.Size = new System.Drawing.Size(30, 13);
|
||||||
|
this.label1.TabIndex = 1;
|
||||||
|
this.label1.Text = "Title:";
|
||||||
|
//
|
||||||
|
// label3
|
||||||
|
//
|
||||||
|
this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.label3.AutoSize = true;
|
||||||
|
this.label3.Location = new System.Drawing.Point(145, 95);
|
||||||
|
this.label3.Name = "label3";
|
||||||
|
this.label3.Size = new System.Drawing.Size(80, 13);
|
||||||
|
this.label3.TabIndex = 3;
|
||||||
|
this.label3.Text = "Rich Presence:";
|
||||||
|
//
|
||||||
|
// label5
|
||||||
|
//
|
||||||
|
this.label5.AutoSize = true;
|
||||||
|
this.label5.Location = new System.Drawing.Point(122, 41);
|
||||||
|
this.label5.Name = "label5";
|
||||||
|
this.label5.Size = new System.Drawing.Size(103, 13);
|
||||||
|
this.label5.TabIndex = 5;
|
||||||
|
this.label5.Text = "Total Earned Points:";
|
||||||
|
//
|
||||||
|
// titleTextBox
|
||||||
|
//
|
||||||
|
this.titleTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.titleTextBox.Location = new System.Drawing.Point(231, 12);
|
||||||
|
this.titleTextBox.Name = "titleTextBox";
|
||||||
|
this.titleTextBox.ReadOnly = true;
|
||||||
|
this.titleTextBox.Size = new System.Drawing.Size(261, 20);
|
||||||
|
this.titleTextBox.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// totalPointsBox
|
||||||
|
//
|
||||||
|
this.totalPointsBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.totalPointsBox.Location = new System.Drawing.Point(231, 38);
|
||||||
|
this.totalPointsBox.Name = "totalPointsBox";
|
||||||
|
this.totalPointsBox.ReadOnly = true;
|
||||||
|
this.totalPointsBox.Size = new System.Drawing.Size(261, 20);
|
||||||
|
this.totalPointsBox.TabIndex = 7;
|
||||||
|
//
|
||||||
|
// currentLboardBox
|
||||||
|
//
|
||||||
|
this.currentLboardBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.currentLboardBox.Location = new System.Drawing.Point(231, 65);
|
||||||
|
this.currentLboardBox.Name = "currentLboardBox";
|
||||||
|
this.currentLboardBox.ReadOnly = true;
|
||||||
|
this.currentLboardBox.Size = new System.Drawing.Size(261, 20);
|
||||||
|
this.currentLboardBox.TabIndex = 8;
|
||||||
|
//
|
||||||
|
// richPresenceBox
|
||||||
|
//
|
||||||
|
this.richPresenceBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.richPresenceBox.Location = new System.Drawing.Point(231, 92);
|
||||||
|
this.richPresenceBox.Name = "richPresenceBox";
|
||||||
|
this.richPresenceBox.ReadOnly = true;
|
||||||
|
this.richPresenceBox.Size = new System.Drawing.Size(261, 20);
|
||||||
|
this.richPresenceBox.TabIndex = 9;
|
||||||
|
//
|
||||||
|
// RCheevosGameInfoForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(504, 121);
|
||||||
|
this.Controls.Add(this.richPresenceBox);
|
||||||
|
this.Controls.Add(this.currentLboardBox);
|
||||||
|
this.Controls.Add(this.totalPointsBox);
|
||||||
|
this.Controls.Add(this.titleTextBox);
|
||||||
|
this.Controls.Add(this.label5);
|
||||||
|
this.Controls.Add(this.label3);
|
||||||
|
this.Controls.Add(this.label2);
|
||||||
|
this.Controls.Add(this.label1);
|
||||||
|
this.Controls.Add(this.gameIconBox);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(300, 160);
|
||||||
|
this.Name = "RCheevosGameInfoForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Game Info";
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.gameIconBox)).EndInit();
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.PictureBox gameIconBox;
|
||||||
|
private System.Windows.Forms.Label label2;
|
||||||
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.Label label3;
|
||||||
|
private System.Windows.Forms.Label label5;
|
||||||
|
private System.Windows.Forms.TextBox titleTextBox;
|
||||||
|
private System.Windows.Forms.TextBox totalPointsBox;
|
||||||
|
private System.Windows.Forms.TextBox currentLboardBox;
|
||||||
|
private System.Windows.Forms.TextBox richPresenceBox;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows current RetroAchievements game info
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosGameInfoForm : Form
|
||||||
|
{
|
||||||
|
public bool IsShown { get; private set; }
|
||||||
|
|
||||||
|
private bool _iconLoaded;
|
||||||
|
|
||||||
|
public RCheevosGameInfoForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
FormClosing += RCheevosGameInfoForm_FormClosing;
|
||||||
|
Shown += (_, _) => IsShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Restart(string gameTitle, int totalPoints, string richPresence)
|
||||||
|
{
|
||||||
|
titleTextBox.Text = gameTitle;
|
||||||
|
totalPointsBox.Text = totalPoints.ToString();
|
||||||
|
currentLboardBox.Text = "N/A";
|
||||||
|
richPresenceBox.Text = richPresence;
|
||||||
|
_iconLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameAdvance(Bitmap gameIcon, int totalPoints, string lboardStr, string richPresence)
|
||||||
|
{
|
||||||
|
// probably bad idea to set this every frame, so
|
||||||
|
if (!_iconLoaded && gameIcon is not null)
|
||||||
|
{
|
||||||
|
gameIconBox.Image = gameIcon;
|
||||||
|
_iconLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPointsBox.Text = totalPoints.ToString();
|
||||||
|
currentLboardBox.Text = lboardStr;
|
||||||
|
richPresenceBox.Text = richPresence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RCheevosGameInfoForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
e.Cancel = true;
|
||||||
|
IsShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
137
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.Designer.cs
generated
Normal file
137
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosLeaderboardForm
|
||||||
|
{
|
||||||
|
/// <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.titleLabel = new System.Windows.Forms.Label();
|
||||||
|
this.descriptionLabel = new System.Windows.Forms.Label();
|
||||||
|
this.titleBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.descriptionBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.scoreLabel = new System.Windows.Forms.Label();
|
||||||
|
this.scoreBox = new System.Windows.Forms.TextBox();
|
||||||
|
this.lowerIsBetterBox = new System.Windows.Forms.CheckBox();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// titleLabel
|
||||||
|
//
|
||||||
|
this.titleLabel.AutoSize = true;
|
||||||
|
this.titleLabel.Location = new System.Drawing.Point(45, 14);
|
||||||
|
this.titleLabel.Name = "titleLabel";
|
||||||
|
this.titleLabel.Size = new System.Drawing.Size(30, 13);
|
||||||
|
this.titleLabel.TabIndex = 1;
|
||||||
|
this.titleLabel.Text = "Title:";
|
||||||
|
//
|
||||||
|
// descriptionLabel
|
||||||
|
//
|
||||||
|
this.descriptionLabel.AutoSize = true;
|
||||||
|
this.descriptionLabel.Location = new System.Drawing.Point(12, 40);
|
||||||
|
this.descriptionLabel.Name = "descriptionLabel";
|
||||||
|
this.descriptionLabel.Size = new System.Drawing.Size(63, 13);
|
||||||
|
this.descriptionLabel.TabIndex = 2;
|
||||||
|
this.descriptionLabel.Text = "Description:";
|
||||||
|
//
|
||||||
|
// titleBox
|
||||||
|
//
|
||||||
|
this.titleBox.Location = new System.Drawing.Point(81, 11);
|
||||||
|
this.titleBox.Name = "titleBox";
|
||||||
|
this.titleBox.ReadOnly = true;
|
||||||
|
this.titleBox.Size = new System.Drawing.Size(411, 20);
|
||||||
|
this.titleBox.TabIndex = 3;
|
||||||
|
//
|
||||||
|
// descriptionBox
|
||||||
|
//
|
||||||
|
this.descriptionBox.Location = new System.Drawing.Point(81, 37);
|
||||||
|
this.descriptionBox.Name = "descriptionBox";
|
||||||
|
this.descriptionBox.ReadOnly = true;
|
||||||
|
this.descriptionBox.Size = new System.Drawing.Size(411, 20);
|
||||||
|
this.descriptionBox.TabIndex = 4;
|
||||||
|
//
|
||||||
|
// scoreLabel
|
||||||
|
//
|
||||||
|
this.scoreLabel.AutoSize = true;
|
||||||
|
this.scoreLabel.Location = new System.Drawing.Point(37, 63);
|
||||||
|
this.scoreLabel.Name = "scoreLabel";
|
||||||
|
this.scoreLabel.Size = new System.Drawing.Size(38, 13);
|
||||||
|
this.scoreLabel.TabIndex = 5;
|
||||||
|
this.scoreLabel.Text = "Score:";
|
||||||
|
//
|
||||||
|
// scoreBox
|
||||||
|
//
|
||||||
|
this.scoreBox.Location = new System.Drawing.Point(81, 60);
|
||||||
|
this.scoreBox.Name = "scoreBox";
|
||||||
|
this.scoreBox.ReadOnly = true;
|
||||||
|
this.scoreBox.Size = new System.Drawing.Size(411, 20);
|
||||||
|
this.scoreBox.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// lowerIsBetterBox
|
||||||
|
//
|
||||||
|
this.lowerIsBetterBox.AutoCheck = false;
|
||||||
|
this.lowerIsBetterBox.AutoSize = true;
|
||||||
|
this.lowerIsBetterBox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
this.lowerIsBetterBox.Location = new System.Drawing.Point(392, 86);
|
||||||
|
this.lowerIsBetterBox.Name = "lowerIsBetterBox";
|
||||||
|
this.lowerIsBetterBox.RightToLeft = System.Windows.Forms.RightToLeft.No;
|
||||||
|
this.lowerIsBetterBox.Size = new System.Drawing.Size(100, 17);
|
||||||
|
this.lowerIsBetterBox.TabIndex = 9;
|
||||||
|
this.lowerIsBetterBox.Text = "Lower Is Better:";
|
||||||
|
this.lowerIsBetterBox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// RCheevosLeaderboardForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(504, 94);
|
||||||
|
this.ControlBox = false;
|
||||||
|
this.Controls.Add(this.lowerIsBetterBox);
|
||||||
|
this.Controls.Add(this.scoreBox);
|
||||||
|
this.Controls.Add(this.scoreLabel);
|
||||||
|
this.Controls.Add(this.descriptionBox);
|
||||||
|
this.Controls.Add(this.titleBox);
|
||||||
|
this.Controls.Add(this.descriptionLabel);
|
||||||
|
this.Controls.Add(this.titleLabel);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(300, 110);
|
||||||
|
this.Name = "RCheevosLeaderboardForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private System.Windows.Forms.Label titleLabel;
|
||||||
|
private System.Windows.Forms.Label descriptionLabel;
|
||||||
|
private System.Windows.Forms.TextBox titleBox;
|
||||||
|
private System.Windows.Forms.TextBox descriptionBox;
|
||||||
|
private System.Windows.Forms.Label scoreLabel;
|
||||||
|
private System.Windows.Forms.TextBox scoreBox;
|
||||||
|
private System.Windows.Forms.CheckBox lowerIsBetterBox;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows information about a specific leaderboard
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosLeaderboardForm : Form
|
||||||
|
{
|
||||||
|
private readonly RCheevos.LBoard _lboard;
|
||||||
|
|
||||||
|
public RCheevosLeaderboardForm(RCheevos.LBoard lboard)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
titleBox.Text = lboard.Title;
|
||||||
|
descriptionBox.Text = lboard.Description;
|
||||||
|
scoreBox.Text = lboard.Score;
|
||||||
|
_lboard = lboard;
|
||||||
|
TopLevel = false;
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameAdvance()
|
||||||
|
{
|
||||||
|
scoreBox.Text = _lboard.Score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
67
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs
generated
Normal file
67
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosLeaderboardListForm
|
||||||
|
{
|
||||||
|
/// <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.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.flowLayoutPanel1.AutoScroll = true;
|
||||||
|
this.flowLayoutPanel1.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.flowLayoutPanel1.MinimumSize = new System.Drawing.Size(544, 0);
|
||||||
|
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
this.flowLayoutPanel1.Size = new System.Drawing.Size(544, 567);
|
||||||
|
this.flowLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// RCheevosLeaderboardListForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(568, 591);
|
||||||
|
this.Controls.Add(this.flowLayoutPanel1);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(574, 244);
|
||||||
|
this.Name = "RCheevosLeaderboardListForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Leaderboard List";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a list of a user's current leaderboards
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosLeaderboardListForm : Form
|
||||||
|
{
|
||||||
|
public bool IsShown { get; private set; }
|
||||||
|
|
||||||
|
private RCheevosLeaderboardForm[] _lboardForms;
|
||||||
|
private int _updateCooldown;
|
||||||
|
|
||||||
|
public RCheevosLeaderboardListForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
FormClosing += RCheevosLeaderboardListForm_FormClosing;
|
||||||
|
Shown += (_, _) => IsShown = true;
|
||||||
|
_lboardForms = Array.Empty<RCheevosLeaderboardForm>();
|
||||||
|
_updateCooldown = 5; // only update every 5 frames / 12 fps (as this is rather expensive to update)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeLboardForms()
|
||||||
|
{
|
||||||
|
foreach (var lboardForm in _lboardForms)
|
||||||
|
{
|
||||||
|
lboardForm.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Restart(IEnumerable<RCheevos.LBoard> lboards)
|
||||||
|
{
|
||||||
|
flowLayoutPanel1.Controls.Clear();
|
||||||
|
DisposeLboardForms();
|
||||||
|
var lboardForms = new List<RCheevosLeaderboardForm>();
|
||||||
|
foreach (var lboard in lboards)
|
||||||
|
{
|
||||||
|
lboardForms.Add(new(lboard));
|
||||||
|
}
|
||||||
|
_lboardForms = lboardForms.ToArray();
|
||||||
|
flowLayoutPanel1.Controls.AddRange(_lboardForms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameAdvance(bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
_updateCooldown--;
|
||||||
|
if (_updateCooldown == 0 || forceUpdate)
|
||||||
|
{
|
||||||
|
_updateCooldown = 5;
|
||||||
|
|
||||||
|
for (int i = 0; i < _lboardForms.Length; i++)
|
||||||
|
{
|
||||||
|
_lboardForms[i].OnFrameAdvance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RCheevosLeaderboardListForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
e.Cancel = true;
|
||||||
|
IsShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
138
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLoginForm.Designer.cs
generated
Normal file
138
src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLoginForm.Designer.cs
generated
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
partial class RCheevosLoginForm
|
||||||
|
{
|
||||||
|
/// <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.btnLogin = new System.Windows.Forms.Button();
|
||||||
|
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
|
||||||
|
this.txtUsername = new System.Windows.Forms.TextBox();
|
||||||
|
this.label1 = new System.Windows.Forms.Label();
|
||||||
|
this.label2 = new System.Windows.Forms.Label();
|
||||||
|
this.txtPassword = new System.Windows.Forms.TextBox();
|
||||||
|
this.label3 = new System.Windows.Forms.Label();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// btnLogin
|
||||||
|
//
|
||||||
|
this.btnLogin.Location = new System.Drawing.Point(270, 117);
|
||||||
|
this.btnLogin.Name = "btnLogin";
|
||||||
|
this.btnLogin.Size = new System.Drawing.Size(83, 23);
|
||||||
|
this.btnLogin.TabIndex = 6;
|
||||||
|
this.btnLogin.Text = "Login";
|
||||||
|
this.btnLogin.UseVisualStyleBackColor = true;
|
||||||
|
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
|
||||||
|
//
|
||||||
|
// linkLabel1
|
||||||
|
//
|
||||||
|
this.linkLabel1.AutoSize = true;
|
||||||
|
this.linkLabel1.Location = new System.Drawing.Point(12, 127);
|
||||||
|
this.linkLabel1.Name = "linkLabel1";
|
||||||
|
this.linkLabel1.Size = new System.Drawing.Size(136, 13);
|
||||||
|
this.linkLabel1.TabIndex = 12;
|
||||||
|
this.linkLabel1.TabStop = true;
|
||||||
|
this.linkLabel1.Text = "No Account? Register here";
|
||||||
|
this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
|
||||||
|
//
|
||||||
|
// txtUsername
|
||||||
|
//
|
||||||
|
this.txtUsername.Location = new System.Drawing.Point(73, 64);
|
||||||
|
this.txtUsername.Name = "txtUsername";
|
||||||
|
this.txtUsername.Size = new System.Drawing.Size(280, 20);
|
||||||
|
this.txtUsername.TabIndex = 15;
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
this.label1.AutoSize = true;
|
||||||
|
this.label1.Location = new System.Drawing.Point(12, 67);
|
||||||
|
this.label1.Name = "label1";
|
||||||
|
this.label1.Size = new System.Drawing.Size(58, 13);
|
||||||
|
this.label1.TabIndex = 16;
|
||||||
|
this.label1.Text = "Username:";
|
||||||
|
//
|
||||||
|
// label2
|
||||||
|
//
|
||||||
|
this.label2.AutoSize = true;
|
||||||
|
this.label2.Location = new System.Drawing.Point(14, 94);
|
||||||
|
this.label2.Name = "label2";
|
||||||
|
this.label2.Size = new System.Drawing.Size(56, 13);
|
||||||
|
this.label2.TabIndex = 17;
|
||||||
|
this.label2.Text = "Password:";
|
||||||
|
//
|
||||||
|
// txtPassword
|
||||||
|
//
|
||||||
|
this.txtPassword.Location = new System.Drawing.Point(73, 91);
|
||||||
|
this.txtPassword.Name = "txtPassword";
|
||||||
|
this.txtPassword.Size = new System.Drawing.Size(280, 20);
|
||||||
|
this.txtPassword.TabIndex = 18;
|
||||||
|
this.txtPassword.UseSystemPasswordChar = true;
|
||||||
|
//
|
||||||
|
// label3
|
||||||
|
//
|
||||||
|
this.label3.AutoSize = true;
|
||||||
|
this.label3.Location = new System.Drawing.Point(19, 9);
|
||||||
|
this.label3.Name = "label3";
|
||||||
|
this.label3.Size = new System.Drawing.Size(334, 39);
|
||||||
|
this.label3.TabIndex = 19;
|
||||||
|
this.label3.Text = "Please enter your RetroAchievements username and password.\r\nNote, your password w" +
|
||||||
|
"ill not be saved, instead a temporary API token\r\nwill be saved and used.\r\n";
|
||||||
|
//
|
||||||
|
// RCheevosLoginForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(367, 150);
|
||||||
|
this.Controls.Add(this.label3);
|
||||||
|
this.Controls.Add(this.txtPassword);
|
||||||
|
this.Controls.Add(this.btnLogin);
|
||||||
|
this.Controls.Add(this.label2);
|
||||||
|
this.Controls.Add(this.linkLabel1);
|
||||||
|
this.Controls.Add(this.label1);
|
||||||
|
this.Controls.Add(this.txtUsername);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(300, 160);
|
||||||
|
this.Name = "RCheevosLoginForm";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Login to RetroAchievements";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private System.Windows.Forms.Button btnLogin;
|
||||||
|
private System.Windows.Forms.LinkLabel linkLabel1;
|
||||||
|
private System.Windows.Forms.TextBox txtUsername;
|
||||||
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.Label label2;
|
||||||
|
private System.Windows.Forms.TextBox txtPassword;
|
||||||
|
private System.Windows.Forms.Label label3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logs into retroachievements.org for RetroAchievements
|
||||||
|
/// </summary>
|
||||||
|
public partial class RCheevosLoginForm : Form
|
||||||
|
{
|
||||||
|
public RCheevosLoginForm(Func<string, string, Task<bool>> loginCallback)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_loginCallback = loginCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Func<string, string, Task<bool>> _loginCallback;
|
||||||
|
|
||||||
|
private async void btnLogin_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var res = await _loginCallback(txtUsername.Text, txtPassword.Text);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Login successful");
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show("Login failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
|
{
|
||||||
|
Process.Start("https://retroachievements.org/createaccount.php");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?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>
|
||||||
|
</root>
|
|
@ -0,0 +1,180 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Atari.Jaguar;
|
||||||
|
using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy;
|
||||||
|
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
||||||
|
using BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.BSNES;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.Gameboy;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.GBHawkLink;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.GBHawkLink3x;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.GBHawkLink4x;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.SNES;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract partial class RetroAchievements
|
||||||
|
{
|
||||||
|
public enum ConsoleID : int
|
||||||
|
{
|
||||||
|
UnknownConsoleID = 0,
|
||||||
|
MegaDrive = 1,
|
||||||
|
N64 = 2,
|
||||||
|
SNES = 3,
|
||||||
|
GB = 4,
|
||||||
|
GBA = 5,
|
||||||
|
GBC = 6,
|
||||||
|
NES = 7,
|
||||||
|
PCEngine = 8,
|
||||||
|
SegaCD = 9,
|
||||||
|
Sega32X = 10,
|
||||||
|
MasterSystem = 11,
|
||||||
|
PlayStation = 12,
|
||||||
|
Lynx = 13,
|
||||||
|
NeoGeoPocket = 14,
|
||||||
|
GameGear = 15,
|
||||||
|
GameCube = 16,
|
||||||
|
Jaguar = 17,
|
||||||
|
DS = 18,
|
||||||
|
WII = 19,
|
||||||
|
WIIU = 20,
|
||||||
|
PlayStation2 = 21,
|
||||||
|
Xbox = 22,
|
||||||
|
MagnavoxOdyssey = 23,
|
||||||
|
PokemonMini = 24,
|
||||||
|
Atari2600 = 25,
|
||||||
|
MSDOS = 26,
|
||||||
|
Arcade = 27,
|
||||||
|
VirtualBoy = 28,
|
||||||
|
MSX = 29,
|
||||||
|
C64 = 30,
|
||||||
|
ZX81 = 31,
|
||||||
|
Oric = 32,
|
||||||
|
SG1000 = 33,
|
||||||
|
VIC20 = 34,
|
||||||
|
Amiga = 35,
|
||||||
|
AtariST = 36,
|
||||||
|
AmstradCPC = 37,
|
||||||
|
AppleII = 38,
|
||||||
|
Saturn = 39,
|
||||||
|
Dreamcast = 40,
|
||||||
|
PSP = 41,
|
||||||
|
CDi = 42,
|
||||||
|
ThreeDO = 43,
|
||||||
|
Colecovision = 44,
|
||||||
|
Intellivision = 45,
|
||||||
|
Vectrex = 46,
|
||||||
|
PC8800 = 47,
|
||||||
|
PC9800 = 48,
|
||||||
|
PCFX = 49,
|
||||||
|
Atari5200 = 50,
|
||||||
|
Atari7800 = 51,
|
||||||
|
X68K = 52,
|
||||||
|
WonderSwan = 53,
|
||||||
|
CassetteVision = 54,
|
||||||
|
SuperCassetteVision = 55,
|
||||||
|
NeoGeoCD = 56,
|
||||||
|
FairchildChannelF = 57,
|
||||||
|
FMTowns = 58,
|
||||||
|
ZXSpectrum = 59,
|
||||||
|
GameAndWatch = 60,
|
||||||
|
NokiaNGage = 61,
|
||||||
|
Nintendo3DS = 62,
|
||||||
|
Supervision = 63,
|
||||||
|
SharpX1 = 64,
|
||||||
|
Tic80 = 65,
|
||||||
|
ThomsonTO8 = 66,
|
||||||
|
PC6000 = 67,
|
||||||
|
Pico = 68,
|
||||||
|
MegaDuck = 69,
|
||||||
|
Zeebo = 70,
|
||||||
|
Arduboy = 71,
|
||||||
|
WASM4 = 72,
|
||||||
|
Arcadia2001 = 73,
|
||||||
|
IntertonVC4000 = 74,
|
||||||
|
ElektorTVGamesComputer = 75,
|
||||||
|
PCEngineCD = 76,
|
||||||
|
JaguarCD = 77,
|
||||||
|
|
||||||
|
NumConsoleIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ConsoleID SystemIdToConsoleId()
|
||||||
|
{
|
||||||
|
return Emu.SystemId switch
|
||||||
|
{
|
||||||
|
VSystemID.Raw.A26 => ConsoleID.Atari2600,
|
||||||
|
VSystemID.Raw.A78 => ConsoleID.Atari7800,
|
||||||
|
VSystemID.Raw.Amiga => ConsoleID.Amiga,
|
||||||
|
VSystemID.Raw.AmstradCPC => ConsoleID.AmstradCPC,
|
||||||
|
VSystemID.Raw.AppleII => ConsoleID.AppleII,
|
||||||
|
VSystemID.Raw.Arcade => ConsoleID.Arcade,
|
||||||
|
VSystemID.Raw.C64 => ConsoleID.C64,
|
||||||
|
VSystemID.Raw.ChannelF => ConsoleID.FairchildChannelF,
|
||||||
|
VSystemID.Raw.Coleco => ConsoleID.Colecovision,
|
||||||
|
VSystemID.Raw.DEBUG => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.Dreamcast => ConsoleID.Dreamcast,
|
||||||
|
VSystemID.Raw.GameCube => ConsoleID.GameCube,
|
||||||
|
VSystemID.Raw.GB when Emu is IGameboyCommon gb => gb.IsCGBMode() ? ConsoleID.GBC : ConsoleID.GB,
|
||||||
|
VSystemID.Raw.GBA => ConsoleID.GBA,
|
||||||
|
VSystemID.Raw.GBC => ConsoleID.GBC, // Not actually used
|
||||||
|
VSystemID.Raw.GBL => Emu switch // actually can be a mix of GB and GBC
|
||||||
|
{
|
||||||
|
// there's probably a better way for all this
|
||||||
|
GambatteLink gb => gb.IsCGBMode(0) ? ConsoleID.GBC : ConsoleID.GB,
|
||||||
|
// WHY ARE THESE PUBLIC???
|
||||||
|
GBHawkLink gb => gb.L.IsCGBMode() ? ConsoleID.GBC : ConsoleID.GB,
|
||||||
|
GBHawkLink3x gb => gb.L.IsCGBMode() ? ConsoleID.GBC : ConsoleID.GB,
|
||||||
|
GBHawkLink4x gb => gb.A.IsCGBMode() ? ConsoleID.GBC : ConsoleID.GB,
|
||||||
|
_ => ConsoleID.UnknownConsoleID,
|
||||||
|
},
|
||||||
|
VSystemID.Raw.GEN when Emu is GPGX gpgx => gpgx.IsMegaCD ? ConsoleID.SegaCD : ConsoleID.MegaDrive,
|
||||||
|
VSystemID.Raw.GEN when Emu is PicoDrive pico => pico.Is32XActive ? ConsoleID.Sega32X : ConsoleID.MegaDrive,
|
||||||
|
VSystemID.Raw.GG => ConsoleID.GameGear,
|
||||||
|
VSystemID.Raw.GGL => ConsoleID.GameGear, // ???
|
||||||
|
VSystemID.Raw.INTV => ConsoleID.Intellivision,
|
||||||
|
VSystemID.Raw.Jaguar when Emu is VirtualJaguar jaguar => jaguar.IsJaguarCD ? ConsoleID.JaguarCD : ConsoleID.Jaguar,
|
||||||
|
VSystemID.Raw.Libretro => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.Lynx => ConsoleID.Lynx,
|
||||||
|
VSystemID.Raw.MSX => ConsoleID.MSX,
|
||||||
|
VSystemID.Raw.N64 => ConsoleID.N64,
|
||||||
|
VSystemID.Raw.NDS => ConsoleID.DS,
|
||||||
|
VSystemID.Raw.NeoGeoCD => ConsoleID.NeoGeoCD,
|
||||||
|
VSystemID.Raw.NES => ConsoleID.NES,
|
||||||
|
VSystemID.Raw.NGP => ConsoleID.NeoGeoPocket,
|
||||||
|
VSystemID.Raw.NULL => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.O2 => ConsoleID.MagnavoxOdyssey,
|
||||||
|
VSystemID.Raw.Panasonic3DO => ConsoleID.ThreeDO,
|
||||||
|
VSystemID.Raw.PCE => ConsoleID.PCEngine,
|
||||||
|
VSystemID.Raw.PCECD => ConsoleID.PCEngineCD,
|
||||||
|
VSystemID.Raw.PCFX => ConsoleID.PCFX,
|
||||||
|
VSystemID.Raw.PhillipsCDi => ConsoleID.CDi,
|
||||||
|
VSystemID.Raw.Playdia => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.PS2 => ConsoleID.PlayStation2,
|
||||||
|
VSystemID.Raw.PSP => ConsoleID.PSP,
|
||||||
|
VSystemID.Raw.PSX => ConsoleID.PlayStation,
|
||||||
|
VSystemID.Raw.SAT => ConsoleID.Saturn,
|
||||||
|
VSystemID.Raw.Sega32X => ConsoleID.Sega32X, // not actually used
|
||||||
|
VSystemID.Raw.SG => ConsoleID.SG1000,
|
||||||
|
VSystemID.Raw.SGB => ConsoleID.GB,
|
||||||
|
VSystemID.Raw.SGX => ConsoleID.PCEngine, // ???
|
||||||
|
VSystemID.Raw.SGXCD => ConsoleID.PCEngineCD, // ???
|
||||||
|
VSystemID.Raw.SMS => ConsoleID.MasterSystem,
|
||||||
|
VSystemID.Raw.SNES => Emu switch
|
||||||
|
{
|
||||||
|
LibsnesCore libsnes => libsnes.IsSGB ? ConsoleID.GB : ConsoleID.SNES,
|
||||||
|
BsnesCore bsnes => bsnes.IsSGB ? ConsoleID.GB : ConsoleID.SNES,
|
||||||
|
_ => ConsoleID.SNES,
|
||||||
|
},
|
||||||
|
VSystemID.Raw.TI83 => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.TIC80 => ConsoleID.Tic80,
|
||||||
|
VSystemID.Raw.UZE => ConsoleID.UnknownConsoleID,
|
||||||
|
VSystemID.Raw.VB => ConsoleID.VirtualBoy,
|
||||||
|
VSystemID.Raw.VEC => ConsoleID.Vectrex,
|
||||||
|
VSystemID.Raw.Wii => ConsoleID.WII,
|
||||||
|
VSystemID.Raw.WSWAN => ConsoleID.WonderSwan,
|
||||||
|
VSystemID.Raw.ZXSpectrum => ConsoleID.ZXSpectrum,
|
||||||
|
_ => ConsoleID.UnknownConsoleID,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,339 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.DiscSystem;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract partial class RetroAchievements
|
||||||
|
{
|
||||||
|
protected bool AllGamesVerified { get; set; }
|
||||||
|
protected abstract int IdentifyHash(string hash);
|
||||||
|
protected abstract int IdentifyRom(byte[] rom);
|
||||||
|
|
||||||
|
private int? HashDisc(string path, ConsoleID consoleID, int discCount)
|
||||||
|
{
|
||||||
|
// this shouldn't throw in practice, this is only called when loading was successful!
|
||||||
|
using var disc = DiscExtensions.CreateAnyType(path, e => throw new Exception(e));
|
||||||
|
var dsr = new DiscSectorReader(disc)
|
||||||
|
{
|
||||||
|
Policy = { DeterministicClearBuffer = false } // let's make this a little faster
|
||||||
|
};
|
||||||
|
|
||||||
|
var buf2048 = new byte[2048];
|
||||||
|
var buffer = new List<byte>();
|
||||||
|
|
||||||
|
switch (consoleID)
|
||||||
|
{
|
||||||
|
case ConsoleID.PCEngineCD:
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(1, buf2048, 0);
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2048, 128 - 22, 22));
|
||||||
|
var bootSector = (buf2048[2] << 16) | (buf2048[1] << 8) | buf2048[0];
|
||||||
|
var numSectors = buf2048[3];
|
||||||
|
for (int i = 0; i < numSectors; i++)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(bootSector + i, buf2048, 0);
|
||||||
|
buffer.AddRange(buf2048);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConsoleID.PCFX:
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(1, buf2048, 0);
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2048, 0, 128));
|
||||||
|
var bootSector = (buf2048[35] << 24) | (buf2048[34] << 16) | (buf2048[33] << 8) | buf2048[32];
|
||||||
|
var numSectors = (buf2048[39] << 24) | (buf2048[38] << 16) | (buf2048[37] << 8) | buf2048[36];
|
||||||
|
for (int i = 0; i < numSectors; i++)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(bootSector + i, buf2048, 0);
|
||||||
|
buffer.AddRange(buf2048);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConsoleID.PlayStation:
|
||||||
|
{
|
||||||
|
int GetFileSector(string filename, out int filesize)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(16, buf2048, 0);
|
||||||
|
var sector = (buf2048[160] << 16) | (buf2048[159] << 8) | buf2048[158];
|
||||||
|
dsr.ReadLBA_2048(sector, buf2048, 0);
|
||||||
|
var index = 0;
|
||||||
|
while ((index + 33 + filename.Length) < 2048)
|
||||||
|
{
|
||||||
|
var term = buf2048[index + 33 + filename.Length];
|
||||||
|
if (term == ';' || term == '\0')
|
||||||
|
{
|
||||||
|
var fn = Encoding.ASCII.GetString(buf2048, index + 33, filename.Length);
|
||||||
|
if (filename == fn)
|
||||||
|
{
|
||||||
|
filesize = (buf2048[index + 13] << 24) | (buf2048[index + 12] << 16) | (buf2048[index + 11] << 8) | buf2048[index + 10];
|
||||||
|
return (buf2048[index + 4] << 16) | (buf2048[index + 3] << 8) | buf2048[index + 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += buf2048[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
filesize = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string exePath = "PSX.EXE";
|
||||||
|
|
||||||
|
// find SYSTEM.CNF sector
|
||||||
|
var sector = GetFileSector("SYSTEM.CNF", out _);
|
||||||
|
if (sector > 0)
|
||||||
|
{
|
||||||
|
// read SYSTEM.CNF sector
|
||||||
|
dsr.ReadLBA_2048(sector, buf2048, 0);
|
||||||
|
exePath = Encoding.ASCII.GetString(buf2048);
|
||||||
|
|
||||||
|
// "BOOT = cdrom:\" precedes the path
|
||||||
|
var index = exePath.IndexOf("BOOT = cdrom:\\");
|
||||||
|
if (index < 0) break;
|
||||||
|
exePath = exePath.Remove(0, index + 14);
|
||||||
|
|
||||||
|
// end of the path has ;
|
||||||
|
var end = exePath.IndexOf(';');
|
||||||
|
if (end < 0) break;
|
||||||
|
exePath = exePath.Substring(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AddRange(Encoding.ASCII.GetBytes(exePath));
|
||||||
|
|
||||||
|
// get the filename
|
||||||
|
// valid too if -1, as that means we already have the filename
|
||||||
|
var start = exePath.LastIndexOf('\\');
|
||||||
|
if (start > 0)
|
||||||
|
{
|
||||||
|
exePath = exePath.Remove(0, start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get sector for exe
|
||||||
|
sector = GetFileSector(exePath, out var exeSize);
|
||||||
|
if (sector < 0) break;
|
||||||
|
|
||||||
|
dsr.ReadLBA_2048(sector++, buf2048, 0);
|
||||||
|
|
||||||
|
if ("PS-X EXE" == Encoding.ASCII.GetString(buf2048, 0, 8))
|
||||||
|
{
|
||||||
|
exeSize = ((buf2048[31] << 24) | (buf2048[30] << 16) | (buf2048[29] << 8) | buf2048[28]) + 2048;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2048, 0, Math.Min(2048, exeSize)));
|
||||||
|
exeSize -= 2048;
|
||||||
|
|
||||||
|
while (exeSize > 0)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2048(sector++, buf2048, 0);
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2048, 0, Math.Min(2048, exeSize)));
|
||||||
|
exeSize -= 2048;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConsoleID.SegaCD:
|
||||||
|
case ConsoleID.Saturn:
|
||||||
|
dsr.ReadLBA_2048(0, buf2048, 0);
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2048, 0, 512));
|
||||||
|
break;
|
||||||
|
case ConsoleID.JaguarCD:
|
||||||
|
if (discCount == 2) // we want to hash the second session of the disc (which is hacked to be disc 2)
|
||||||
|
{
|
||||||
|
const string _jaguarHeader = "ATARI APPROVED DATA HEADER ATRI ";
|
||||||
|
const string _jaguarBSHeader = "TARA IPARPVODED TA AEHDAREA RT I";
|
||||||
|
var buf2352 = new byte[2352];
|
||||||
|
|
||||||
|
// find the boot track header
|
||||||
|
// see https://github.com/TASEmulators/BizHawk/blob/f29113287e88c6a644dbff30f92a9833307aad20/waterbox/virtualjaguar/src/cdhle.cpp#L109-L145
|
||||||
|
var startLba = disc.Session1.FirstInformationTrack.LBA;
|
||||||
|
var numLbas = disc.Session1.FirstInformationTrack.NextTrack.LBA - disc.Session1.FirstInformationTrack.LBA;
|
||||||
|
int bootLen = 0, bootLba = 0, bootOff = 0;
|
||||||
|
bool byteswapped = false, foundHeader = false;
|
||||||
|
for (int i = 0; i < numLbas; i++)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2352(startLba + i, buf2352, 0);
|
||||||
|
|
||||||
|
for (int j = 0; j < (2352 - 32 - 4 - 4); j++)
|
||||||
|
{
|
||||||
|
if (buf2352[j] == _jaguarHeader[0])
|
||||||
|
{
|
||||||
|
if (_jaguarHeader == Encoding.ASCII.GetString(buf2352, j, 32))
|
||||||
|
{
|
||||||
|
bootLen = (buf2352[j + 36] << 24) | (buf2352[j + 37] << 16) | (buf2352[j + 38] << 8) | buf2352[j + 39];
|
||||||
|
bootLba = startLba + i;
|
||||||
|
bootOff = j + 32 + 4 + 4;
|
||||||
|
byteswapped = false;
|
||||||
|
foundHeader = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (buf2352[j] == _jaguarBSHeader[0])
|
||||||
|
{
|
||||||
|
if (_jaguarBSHeader == Encoding.ASCII.GetString(buf2352, j, 32))
|
||||||
|
{
|
||||||
|
bootLen = (buf2352[j + 37] << 24) | (buf2352[j + 36] << 16) | (buf2352[j + 39] << 8) | buf2352[j + 38];
|
||||||
|
bootLba = startLba + i;
|
||||||
|
bootOff = j + 32 + 4 + 4;
|
||||||
|
byteswapped = true;
|
||||||
|
foundHeader = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundHeader)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundHeader)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsr.ReadLBA_2352(bootLba++, buf2352, 0);
|
||||||
|
|
||||||
|
if (byteswapped)
|
||||||
|
{
|
||||||
|
EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2352, bootOff, Math.Min(2352 - bootOff, bootLen)));
|
||||||
|
bootLen -= 2352 - bootOff;
|
||||||
|
|
||||||
|
while (bootLen > 0)
|
||||||
|
{
|
||||||
|
dsr.ReadLBA_2352(bootLba++, buf2352, 0);
|
||||||
|
|
||||||
|
if (byteswapped)
|
||||||
|
{
|
||||||
|
EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AddRange(new ArraySegment<byte>(buf2352, 0, Math.Min(2352, bootLen)));
|
||||||
|
bootLen -= 2352;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null; // other sessions aren't hashed, ignore them
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = MD5Checksum.ComputeDigestHex(buffer.ToArray());
|
||||||
|
return IdentifyHash(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int HashArcade(string path)
|
||||||
|
{
|
||||||
|
// Arcade wants to just hash the filename (with no extension)
|
||||||
|
var name = Encoding.UTF8.GetBytes(Path.GetFileNameWithoutExtension(path));
|
||||||
|
var hash = MD5Checksum.ComputeDigestHex(name);
|
||||||
|
return IdentifyHash(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IReadOnlyList<int> GetRAGameIds(IOpenAdvanced ioa, ConsoleID consoleID)
|
||||||
|
{
|
||||||
|
var ret = new List<int>();
|
||||||
|
switch (ioa.TypeName)
|
||||||
|
{
|
||||||
|
case OpenAdvancedTypes.OpenRom:
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(Path.GetExtension(ioa.SimplePath.Replace("|", "")).ToLowerInvariant());
|
||||||
|
var discCount = 0;
|
||||||
|
|
||||||
|
if (ext == ".m3u")
|
||||||
|
{
|
||||||
|
using var file = new HawkFile(ioa.SimplePath);
|
||||||
|
using var sr = new StreamReader(file.GetStream());
|
||||||
|
var m3u = M3U_File.Read(sr);
|
||||||
|
m3u.Rebase(Path.GetDirectoryName(ioa.SimplePath));
|
||||||
|
foreach (var entry in m3u.Entries)
|
||||||
|
{
|
||||||
|
var id = HashDisc(entry.Path, consoleID, ++discCount);
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
ret.Add(id.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ext == ".xml")
|
||||||
|
{
|
||||||
|
var xml = XmlGame.Create(new HawkFile(ioa.SimplePath));
|
||||||
|
foreach (var kvp in xml.Assets)
|
||||||
|
{
|
||||||
|
if (consoleID is ConsoleID.Arcade)
|
||||||
|
{
|
||||||
|
ret.Add(HashArcade(kvp.Key));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Disc.IsValidExtension(Path.GetExtension(kvp.Key)))
|
||||||
|
{
|
||||||
|
var id = HashDisc(kvp.Key, consoleID, ++discCount);
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
ret.Add(id.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret.Add(IdentifyRom(kvp.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (consoleID is ConsoleID.Arcade)
|
||||||
|
{
|
||||||
|
ret.Add(HashArcade(ioa.SimplePath));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Disc.IsValidExtension(Path.GetExtension(ext)))
|
||||||
|
{
|
||||||
|
var id = HashDisc(ioa.SimplePath, consoleID, ++discCount);
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
ret.Add(id.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var file = new HawkFile(ioa.SimplePath);
|
||||||
|
var rom = file.ReadAllBytes();
|
||||||
|
ret.Add(IdentifyRom(rom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OpenAdvancedTypes.MAME:
|
||||||
|
{
|
||||||
|
ret.Add(HashArcade(ioa.SimplePath));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OpenAdvancedTypes.LibretroNoGame:
|
||||||
|
// nothing to hash here
|
||||||
|
break;
|
||||||
|
case OpenAdvancedTypes.Libretro:
|
||||||
|
{
|
||||||
|
// can't know what's here exactly, so we'll just hash the entire thing
|
||||||
|
using var file = new HawkFile(ioa.SimplePath);
|
||||||
|
var rom = file.ReadAllBytes();
|
||||||
|
ret.Add(IdentifyRom(rom));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Atari.Atari2600;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.MSX;
|
||||||
|
using BizHawk.Emulation.Cores.Consoles.O2Hawk;
|
||||||
|
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.BSNES;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.Gameboy;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.GBA;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.NES;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.Sameboy;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.SNES;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.SNES9X;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.SubGBHawk;
|
||||||
|
using BizHawk.Emulation.Cores.Nintendo.SubNESHawk;
|
||||||
|
using BizHawk.Emulation.Cores.PCEngine;
|
||||||
|
using BizHawk.Emulation.Cores.Sega.MasterSystem;
|
||||||
|
using BizHawk.Emulation.Cores.Waterbox;
|
||||||
|
using BizHawk.Emulation.Cores.WonderSwan;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract partial class RetroAchievements
|
||||||
|
{
|
||||||
|
// "Hardcore Mode" is a mode intended for RA's leaderboard, and places various restrictions on the emulator
|
||||||
|
// To keep changes outside this file minimal, we'll simply check if any problematic condition arises and disable hardcore mode
|
||||||
|
// (with the exception of frame advance and rewind, which we can just suppress)
|
||||||
|
|
||||||
|
private static readonly Type[] HardcoreProhibitedTools = new[]
|
||||||
|
{
|
||||||
|
typeof(LuaConsole), typeof(RamWatch), typeof(RamSearch),
|
||||||
|
typeof(GameShark), typeof(SNESGraphicsDebugger), typeof(PceBgViewer),
|
||||||
|
typeof(PceTileViewer), typeof(GenVdpViewer), typeof(SmsVdpViewer),
|
||||||
|
typeof(PCESoundDebugger), typeof(MacroInputTool), typeof(GenericDebugger),
|
||||||
|
typeof(NESNameTableViewer), typeof(TraceLogger), typeof(CDL),
|
||||||
|
typeof(Cheats), typeof(NesPPU), typeof(GbaGpuView),
|
||||||
|
typeof(GbGpuView), typeof(BasicBot), typeof(HexEditor),
|
||||||
|
typeof(TAStudio),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, string[]> CoreGraphicsLayers = new()
|
||||||
|
{
|
||||||
|
[typeof(MSX)] = new[] { "DispBG", "DispOBJ" },
|
||||||
|
[typeof(Atari2600)] = new[] { "ShowBG", "ShowPlayer1", "ShowPlayer2", "ShowMissle1", "ShowMissle2", "ShowBall", "ShowPlayfield" },
|
||||||
|
[typeof(O2Hawk)] = new[] { "Show_Chars", "Show_Quads", "Show_Sprites", "Show_G7400_Sprites", "Show_G7400_BG" },
|
||||||
|
[typeof(BsnesCore)] = new[] { "ShowBG1_0", "ShowBG2_0", "ShowBG3_0", "ShowBG4_0", "ShowBG1_1", "ShowBG2_1", "ShowBG3_1", "ShowBG4_1", "ShowOBJ_0", "ShowOBJ_1", "ShowOBJ_2", "ShowOBJ_3" },
|
||||||
|
[typeof(MGBAHawk)] = new[] { "DisplayBG0", "DisplayBG1", "DisplayBG2", "DisplayBG3", "DisplayOBJ" },
|
||||||
|
[typeof(NES)] = new[] { "DispBackground", "DispSprites" },
|
||||||
|
[typeof(Sameboy)] = new[] { "EnableBGWIN", "EnableOBJ" },
|
||||||
|
[typeof(LibsnesCore)] = new[] { "ShowBG1_0", "ShowBG2_0", "ShowBG3_0", "ShowBG4_0", "ShowBG1_1", "ShowBG2_1", "ShowBG3_1", "ShowBG4_1", "ShowOBJ_0", "ShowOBJ_1", "ShowOBJ_2", "ShowOBJ_3" },
|
||||||
|
[typeof(Snes9x)] = new[] { "ShowBg0", "ShowBg1", "ShowBg2", "ShowBg3", "ShowSprites0", "ShowSprites1", "ShowSprites2", "ShowSprites3", "ShowWindow", "ShowTransparency" },
|
||||||
|
[typeof(PCEngine)] = new[] { "ShowBG1", "ShowOBJ1", "ShowBG2", "ShowOBJ2", },
|
||||||
|
[typeof(GPGX)] = new[] { "DrawBGA", "DrawBGB", "DrawBGW", "DrawObj", },
|
||||||
|
[typeof(SMS)] = new[] { "DispBG", "DispOBJ," },
|
||||||
|
[typeof(WonderSwan)] = new[] { "EnableBG", "EnableFG", "EnableSprites", },
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly OverrideAdapter _hardcoreHotkeyOverrides = new();
|
||||||
|
|
||||||
|
protected abstract void HandleHardcoreModeDisable(string reason);
|
||||||
|
|
||||||
|
protected void CheckHardcoreModeConditions()
|
||||||
|
{
|
||||||
|
if (!AllGamesVerified)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable("All loaded games were not verified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MovieSession.Movie.IsPlaying())
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable("Playing a movie while in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppress rewind and frame advance hotkeys
|
||||||
|
_hardcoreHotkeyOverrides.FrameTick();
|
||||||
|
_hardcoreHotkeyOverrides.SetButton("Frame Advance", false);
|
||||||
|
_hardcoreHotkeyOverrides.SetButton("Rewind", false);
|
||||||
|
_inputManager.ClientControls.Overrides(_hardcoreHotkeyOverrides);
|
||||||
|
_mainForm.FrameInch = false;
|
||||||
|
|
||||||
|
var fastForward = _inputManager.ClientControls["Fast Forward"] || _mainForm.FastForward;
|
||||||
|
var speedPercent = fastForward ? _getConfig().SpeedPercentAlternate : _getConfig().SpeedPercent;
|
||||||
|
if (speedPercent < 100)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable("Slow motion in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var t in HardcoreProhibitedTools)
|
||||||
|
{
|
||||||
|
if (_tools.IsLoaded(t))
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Using {t.Name} in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't know what external tools are doing, so just don't allow them here
|
||||||
|
if (_tools.IsLoaded<IExternalToolForm>())
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Using external tools in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Emu is SubNESHawk or SubBsnesCore or SubGBHawk)
|
||||||
|
{
|
||||||
|
// this is mostly due to wonkiness with subframes which can be used as pseudo slowdown
|
||||||
|
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (Emu is NymaCore nyma)
|
||||||
|
{
|
||||||
|
if (nyma.GetSettings().DisabledLayers.Any())
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s graphics layers in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Emu is GambatteLink gl)
|
||||||
|
{
|
||||||
|
foreach (var ss in gl.GetSyncSettings()._linkedSyncSettings)
|
||||||
|
{
|
||||||
|
if (!ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Disabling GambatteLink's graphics layers in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Emu is Gameboy gb)
|
||||||
|
{
|
||||||
|
var ss = gb.GetSyncSettings();
|
||||||
|
if (!ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Disabling Gambatte's graphics layers in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ss.FrameLength is Gameboy.GambatteSyncSettings.FrameLengthType.UserDefinedFrames)
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (CoreGraphicsLayers.TryGetValue(Emu.GetType(), out var layers))
|
||||||
|
{
|
||||||
|
var s = _mainForm.GetSettingsAdapterForLoadedCoreUntyped().GetSettings();
|
||||||
|
var t = s.GetType();
|
||||||
|
foreach (var layer in layers)
|
||||||
|
{
|
||||||
|
// annoyingly NES has fields instead of properties for layers
|
||||||
|
if (!(bool)(t.GetProperty(layer)?.GetValue(s) ?? t.GetField(layer).GetValue(s)))
|
||||||
|
{
|
||||||
|
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s {layer} in hardcore mode is not allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,524 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract partial class RetroAchievements
|
||||||
|
{
|
||||||
|
public struct RAMemGuard : IMonitor, IDisposable
|
||||||
|
{
|
||||||
|
private readonly AutoResetEvent _start, _go, _end;
|
||||||
|
private readonly ThreadLocal<bool> _isMainThread;
|
||||||
|
|
||||||
|
private bool IsNotMainThread => !_isMainThread.Value;
|
||||||
|
|
||||||
|
public RAMemGuard(AutoResetEvent start, AutoResetEvent go, AutoResetEvent end)
|
||||||
|
{
|
||||||
|
_start = start;
|
||||||
|
_go = go;
|
||||||
|
_end = end;
|
||||||
|
_isMainThread = new() { Value = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_start.Dispose();
|
||||||
|
_go.Dispose();
|
||||||
|
_end.Dispose();
|
||||||
|
_isMainThread.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enter()
|
||||||
|
{
|
||||||
|
if (IsNotMainThread)
|
||||||
|
{
|
||||||
|
_start.Set();
|
||||||
|
_go.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Exit()
|
||||||
|
{
|
||||||
|
if (IsNotMainThread)
|
||||||
|
{
|
||||||
|
_end.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate byte ReadMemoryFunc(int address);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void WriteMemoryFunc(int address, byte value);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int ReadMemoryBlockFunc(int address, IntPtr buffer, int bytes);
|
||||||
|
|
||||||
|
public class MemFunctions
|
||||||
|
{
|
||||||
|
protected readonly MemoryDomain _domain;
|
||||||
|
private readonly int _domainAddrStart; // addr of _domain where bank begins
|
||||||
|
private readonly int _addressMangler; // of course, let's *not* correct internal core byteswapping!
|
||||||
|
|
||||||
|
public ReadMemoryFunc ReadFunc { get; protected init; }
|
||||||
|
public WriteMemoryFunc WriteFunc { get; protected init; }
|
||||||
|
public ReadMemoryBlockFunc ReadBlockFunc { get; protected init; }
|
||||||
|
|
||||||
|
public readonly int BankSize;
|
||||||
|
|
||||||
|
public RAMemGuard? MemGuard { get; set; }
|
||||||
|
|
||||||
|
protected virtual int FixAddr(int addr)
|
||||||
|
=> _domainAddrStart + addr;
|
||||||
|
|
||||||
|
protected virtual byte ReadMem(int addr)
|
||||||
|
{
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
return _domain.PeekByte(FixAddr(addr) ^ _addressMangler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void WriteMem(int addr, byte val)
|
||||||
|
{
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
_domain.PokeByte(FixAddr(addr) ^ _addressMangler, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual int ReadMemBlock(int addr, IntPtr buffer, int bytes)
|
||||||
|
{
|
||||||
|
addr = FixAddr(addr);
|
||||||
|
|
||||||
|
if (addr >= (_domainAddrStart + BankSize))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
var end = Math.Min(addr + bytes, _domainAddrStart + BankSize);
|
||||||
|
var length = end - addr;
|
||||||
|
|
||||||
|
if (_addressMangler == 0)
|
||||||
|
{
|
||||||
|
var ret = new byte[length];
|
||||||
|
_domain.BulkPeekByte(((long)addr).RangeToExclusive(end), ret);
|
||||||
|
Marshal.Copy(ret, 0, buffer, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
for (var i = addr; i < end; i++)
|
||||||
|
{
|
||||||
|
((byte*)buffer)[i - addr] = _domain.PeekByte(i ^ _addressMangler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemFunctions(MemoryDomain domain, int domainAddrStart, long bankSize, int addressMangler = 0)
|
||||||
|
{
|
||||||
|
_domain = domain;
|
||||||
|
_domainAddrStart = domainAddrStart;
|
||||||
|
_addressMangler = addressMangler;
|
||||||
|
|
||||||
|
ReadFunc = ReadMem;
|
||||||
|
WriteFunc = WriteMem;
|
||||||
|
ReadBlockFunc = ReadMemBlock;
|
||||||
|
|
||||||
|
if (bankSize > int.MaxValue)
|
||||||
|
{
|
||||||
|
throw new OverflowException("bankSize is too big!");
|
||||||
|
}
|
||||||
|
|
||||||
|
BankSize = (int)bankSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullMemFunctions : MemFunctions
|
||||||
|
{
|
||||||
|
public NullMemFunctions(long bankSize)
|
||||||
|
: base(null, 0, bankSize)
|
||||||
|
{
|
||||||
|
ReadFunc = null;
|
||||||
|
WriteFunc = null;
|
||||||
|
ReadBlockFunc = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a complete hack because the libretro Intelli core sucks and so achievements are made expecting this format
|
||||||
|
private class IntelliMemFunctions : MemFunctions
|
||||||
|
{
|
||||||
|
protected override int FixAddr(int addr)
|
||||||
|
=> (addr >> 1) + (~addr & 1);
|
||||||
|
|
||||||
|
protected override byte ReadMem(int addr)
|
||||||
|
{
|
||||||
|
if ((addr & 2) != 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.ReadMem(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void WriteMem(int addr, byte val)
|
||||||
|
{
|
||||||
|
if ((addr & 2) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.WriteMem(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int ReadMemBlock(int addr, IntPtr buffer, int bytes)
|
||||||
|
{
|
||||||
|
if (addr >= BankSize)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
var end = Math.Min(addr + bytes, BankSize);
|
||||||
|
var length = end - addr;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
for (var i = addr; i < end; i++)
|
||||||
|
{
|
||||||
|
if ((i & 2) != 0)
|
||||||
|
{
|
||||||
|
((byte*)buffer)[i - addr] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
((byte*)buffer)[i - addr] = _domain.PeekByte(FixAddr(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntelliMemFunctions(MemoryDomain domain)
|
||||||
|
: base(domain, 0, 0x40000)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChanFMemFunctions : MemFunctions
|
||||||
|
{
|
||||||
|
private readonly IDebuggable _debuggable;
|
||||||
|
private readonly MemoryDomain _vram; // our vram is unpacked, but RA expects it packed
|
||||||
|
|
||||||
|
private byte ReadVRAMPacked(int addr)
|
||||||
|
{
|
||||||
|
return (byte)(((_vram.PeekByte(addr * 4 + 0) & 3) << 6)
|
||||||
|
| ((_vram.PeekByte(addr * 4 + 1) & 3) << 4)
|
||||||
|
| ((_vram.PeekByte(addr * 4 + 2) & 3) << 2)
|
||||||
|
| ((_vram.PeekByte(addr * 4 + 3) & 3) << 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override byte ReadMem(int addr)
|
||||||
|
{
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
if (addr < 0x40)
|
||||||
|
{
|
||||||
|
return (byte)_debuggable.GetCpuFlagsAndRegisters()["SPR" + addr].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ReadVRAMPacked(addr - 0x40);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void WriteMem(int addr, byte val)
|
||||||
|
{
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
if (addr < 0x40)
|
||||||
|
{
|
||||||
|
_debuggable.SetCpuRegister("SPR" + addr, val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addr -= 0x40;
|
||||||
|
_vram.PokeByte(addr * 4 + 0, (byte)((val >> 6) & 3));
|
||||||
|
_vram.PokeByte(addr * 4 + 1, (byte)((val >> 4) & 3));
|
||||||
|
_vram.PokeByte(addr * 4 + 2, (byte)((val >> 2) & 3));
|
||||||
|
_vram.PokeByte(addr * 4 + 3, (byte)((val >> 0) & 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int ReadMemBlock(int addr, IntPtr buffer, int bytes)
|
||||||
|
{
|
||||||
|
if (addr >= BankSize)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (MemGuard?.EnterExit())
|
||||||
|
{
|
||||||
|
var regs = _debuggable.GetCpuFlagsAndRegisters();
|
||||||
|
var end = Math.Min(addr + bytes, BankSize);
|
||||||
|
for (int i = addr; i < end; i++)
|
||||||
|
{
|
||||||
|
byte val;
|
||||||
|
if (i < 0x40)
|
||||||
|
{
|
||||||
|
val = (byte)regs["SPR" + i].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val = ReadVRAMPacked(i - 0x40);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
((byte*)buffer)[i - addr] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return end - addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChanFMemFunctions(IDebuggable debuggable, MemoryDomain vram)
|
||||||
|
: base(null, 0, 0x840)
|
||||||
|
{
|
||||||
|
_debuggable = debuggable;
|
||||||
|
_vram = vram;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// these consoles will use the entire system bus
|
||||||
|
private static readonly ConsoleID[] UseFullSysBus = new[]
|
||||||
|
{
|
||||||
|
ConsoleID.NES, ConsoleID.C64, ConsoleID.AmstradCPC, ConsoleID.Atari7800,
|
||||||
|
};
|
||||||
|
|
||||||
|
// these consoles will use the entire main memory domain
|
||||||
|
private static readonly ConsoleID[] UseFullMainMem = new[]
|
||||||
|
{
|
||||||
|
ConsoleID.PlayStation, ConsoleID.Lynx, ConsoleID.Lynx, ConsoleID.NeoGeoPocket,
|
||||||
|
ConsoleID.Jaguar, ConsoleID.JaguarCD, ConsoleID.DS, ConsoleID.AppleII,
|
||||||
|
ConsoleID.Vectrex, ConsoleID.Tic80, ConsoleID.PCEngine,
|
||||||
|
};
|
||||||
|
|
||||||
|
// these consoles will use part of the system bus at an offset
|
||||||
|
private static readonly Dictionary<ConsoleID, (int Start, int Size)[]> UsePartialSysBus = new()
|
||||||
|
{
|
||||||
|
[ConsoleID.MasterSystem] = new[] { (0xC000, 0x2000) },
|
||||||
|
[ConsoleID.GameGear] = new[] { (0xC000, 0x2000) },
|
||||||
|
[ConsoleID.Atari2600] = new[] { (0, 0x80) },
|
||||||
|
[ConsoleID.Colecovision] = new[] { (0x6000, 0x400) },
|
||||||
|
[ConsoleID.GBA] = new[] { (0x3000000, 0x8000), (0x2000000, 0x40000) },
|
||||||
|
[ConsoleID.SG1000] = new[] { (0xC000, 0x2000), (0x2000, 0x2000), (0x8000, 0x2000) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// anything more complicated will be handled accordingly
|
||||||
|
|
||||||
|
protected static IReadOnlyList<MemFunctions> CreateMemoryBanks(ConsoleID consoleId, IMemoryDomains domains, IDebuggable debuggable)
|
||||||
|
{
|
||||||
|
var mfs = new List<MemFunctions>();
|
||||||
|
|
||||||
|
void TryAddDomain(string domain, int? size = null, int addressMangler = 0)
|
||||||
|
{
|
||||||
|
if (domains.Has(domain))
|
||||||
|
{
|
||||||
|
if (size.HasValue && domains[domain].Size < size.Value)
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains[domain], 0, domains[domain].Size, addressMangler));
|
||||||
|
mfs.Add(new NullMemFunctions(size.Value - domains[domain].Size));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains[domain], 0, size ?? domains[domain].Size, addressMangler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (size.HasValue)
|
||||||
|
{
|
||||||
|
mfs.Add(new NullMemFunctions(size.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.Exists(UseFullSysBus, id => id == consoleId))
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains.SystemBus, 0, domains.SystemBus.Size));
|
||||||
|
}
|
||||||
|
else if (Array.Exists(UseFullMainMem, id => id == consoleId))
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains.MainMemory, 0, domains.MainMemory.Size));
|
||||||
|
}
|
||||||
|
else if (UsePartialSysBus.TryGetValue(consoleId, out var pairs))
|
||||||
|
{
|
||||||
|
foreach (var pair in pairs)
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains.SystemBus, pair.Start, pair.Size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (consoleId)
|
||||||
|
{
|
||||||
|
case ConsoleID.MegaDrive:
|
||||||
|
case ConsoleID.Sega32X:
|
||||||
|
mfs.Add(new(domains["68K RAM"], 0, domains["68K RAM"].Size, 1));
|
||||||
|
TryAddDomain("32X RAM", addressMangler: 1);
|
||||||
|
// our picodrive doesn't byteswap its SRAM, so...
|
||||||
|
TryAddDomain("SRAM", addressMangler: domains["SRAM"] is MemoryDomainIntPtrSwap16Monitor ? 1 : 0);
|
||||||
|
break;
|
||||||
|
case ConsoleID.SNES:
|
||||||
|
mfs.Add(new(domains["WRAM"], 0, domains["WRAM"].Size));
|
||||||
|
TryAddDomain("CARTRAM");
|
||||||
|
// sufami B sram
|
||||||
|
// don't think this is actually hooked up at all anyways...
|
||||||
|
TryAddDomain("CARTRAM B"); // Snes9x
|
||||||
|
TryAddDomain("SUFAMI TURBO B RAM"); // new BSNES
|
||||||
|
break;
|
||||||
|
case ConsoleID.GB:
|
||||||
|
case ConsoleID.GBC:
|
||||||
|
if (domains.Has("SGB CARTROM"))
|
||||||
|
{
|
||||||
|
// old BSNES doesn't have as many domains
|
||||||
|
// but it should still suffice in practice
|
||||||
|
mfs.Add(new(domains["SGB CARTROM"], 0, 0x8000));
|
||||||
|
TryAddDomain("SGB VRAM", 0x2000);
|
||||||
|
TryAddDomain("SGB CARTRAM", 0x2000);
|
||||||
|
mfs.Add(new(domains["SGB WRAM"], 0, 0x2000));
|
||||||
|
mfs.Add(new(domains["SGB WRAM"], 0, 0x1E00));
|
||||||
|
TryAddDomain("SGB OAM", 0xA0);
|
||||||
|
TryAddDomain("SGB System Bus", 0xE0);
|
||||||
|
TryAddDomain("SGB HRAM", 0x80);
|
||||||
|
TryAddDomain("SGB IE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string sysBus, cartRam, wram;
|
||||||
|
if (domains.Has("P1 System Bus")) // GambatteLink
|
||||||
|
{
|
||||||
|
sysBus = "P1 System Bus";
|
||||||
|
cartRam = "P1 CartRAM";
|
||||||
|
wram = "P1 WRAM";
|
||||||
|
}
|
||||||
|
else if (domains.Has("System Bus L")) // GBHawkLink / GBHawkLink3x
|
||||||
|
{
|
||||||
|
sysBus = "System Bus L";
|
||||||
|
cartRam = "Cart RAM L";
|
||||||
|
wram = "Main RAM L";
|
||||||
|
}
|
||||||
|
else if (domains.Has("System Bus A")) // GBHawkLink4x
|
||||||
|
{
|
||||||
|
sysBus = "System Bus A";
|
||||||
|
cartRam = "Cart RAM A";
|
||||||
|
wram = "Main RAM A";
|
||||||
|
}
|
||||||
|
else // Gambatte / GBHawk
|
||||||
|
{
|
||||||
|
sysBus = "System Bus";
|
||||||
|
cartRam = "CartRAM";
|
||||||
|
wram = "WRAM";
|
||||||
|
}
|
||||||
|
|
||||||
|
mfs.Add(new(domains[sysBus], 0, 0xA000));
|
||||||
|
TryAddDomain(cartRam, 0x2000);
|
||||||
|
mfs.Add(new(domains[wram], 0x0000, 0x2000));
|
||||||
|
mfs.Add(new(domains[sysBus], 0xE000, 0x2000));
|
||||||
|
if (domains[wram].Size == 0x8000)
|
||||||
|
{
|
||||||
|
mfs.Add(new(domains[wram], 0x2000, 0x6000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ConsoleID.SegaCD:
|
||||||
|
mfs.Add(new(domains["68K RAM"], 0, domains["68K RAM"].Size, 1));
|
||||||
|
mfs.Add(new(domains["CD PRG RAM"], 0, domains["CD PRG RAM"].Size, 1));
|
||||||
|
break;
|
||||||
|
case ConsoleID.MagnavoxOdyssey:
|
||||||
|
mfs.Add(new(domains["CPU RAM"], 0, domains["CPU RAM"].Size));
|
||||||
|
mfs.Add(new(domains["Main RAM"], 0, domains["Main RAM"].Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.VirtualBoy:
|
||||||
|
// todo: add System Bus so this isn't needed
|
||||||
|
mfs.Add(new(domains["WRAM"], 0, domains["WRAM"].Size));
|
||||||
|
mfs.Add(new(domains["CARTRAM"], 0, domains["CARTRAM"].Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.MSX:
|
||||||
|
// no, can't use MainMemory here, as System Bus is that due to init ordering
|
||||||
|
// todo: make this MainMemory
|
||||||
|
mfs.Add(new(domains["RAM"], 0, domains["RAM"].Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.Saturn:
|
||||||
|
// todo: add System Bus so this isn't needed
|
||||||
|
mfs.Add(new(domains["Work Ram Low"], 0, domains["Work Ram Low"].Size));
|
||||||
|
mfs.Add(new(domains["Work Ram High"], 0, domains["Work Ram High"].Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.Intellivision:
|
||||||
|
// special case
|
||||||
|
mfs.Add(new NullMemFunctions(0x80));
|
||||||
|
mfs.Add(new IntelliMemFunctions(domains.SystemBus));
|
||||||
|
break;
|
||||||
|
case ConsoleID.PCFX:
|
||||||
|
// todo: add System Bus so this isn't needed
|
||||||
|
mfs.Add(new(domains["Main RAM"], 0, domains["Main RAM"].Size));
|
||||||
|
mfs.Add(new(domains["Backup RAM"], 0, domains["Backup RAM"].Size));
|
||||||
|
mfs.Add(new(domains["Extra Backup RAM"], 0, domains["Extra Backup RAM"].Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.WonderSwan:
|
||||||
|
mfs.Add(new(domains["RAM"], 0, domains["RAM"].Size));
|
||||||
|
TryAddDomain("SRAM");
|
||||||
|
TryAddDomain("EEPROM");
|
||||||
|
break;
|
||||||
|
case ConsoleID.FairchildChannelF:
|
||||||
|
// special case
|
||||||
|
mfs.Add(new ChanFMemFunctions(debuggable, domains["VRAM"]));
|
||||||
|
mfs.Add(new(domains.SystemBus, 0, domains.SystemBus.Size));
|
||||||
|
break;
|
||||||
|
case ConsoleID.PCEngineCD:
|
||||||
|
mfs.Add(new(domains["System Bus (21 bit)"], 0x1F0000, 0x2000));
|
||||||
|
mfs.Add(new(domains["System Bus (21 bit)"], 0x100000, 0x10000));
|
||||||
|
mfs.Add(new(domains["System Bus (21 bit)"], 0xD0000, 0x30000));
|
||||||
|
mfs.Add(new(domains["System Bus (21 bit)"], 0x1EE000, 0x800));
|
||||||
|
break;
|
||||||
|
case ConsoleID.N64:
|
||||||
|
mfs.Add(new(domains.MainMemory, 0, domains.MainMemory.Size, 3));
|
||||||
|
break;
|
||||||
|
case ConsoleID.Arcade:
|
||||||
|
foreach (var domain in domains)
|
||||||
|
{
|
||||||
|
if (domain.Name.Contains("ram"))
|
||||||
|
{
|
||||||
|
mfs.Add(new(domain, 0, domain.Size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ConsoleID.UnknownConsoleID:
|
||||||
|
case ConsoleID.ZXSpectrum: // this doesn't actually have anything standardized, so...
|
||||||
|
default:
|
||||||
|
mfs.Add(new(domains.MainMemory, 0, domains.MainMemory.Size));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mfs.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
{
|
||||||
|
public abstract partial class RetroAchievements : IRetroAchievements
|
||||||
|
{
|
||||||
|
protected readonly IMainFormForRetroAchievements _mainForm;
|
||||||
|
protected readonly InputManager _inputManager;
|
||||||
|
protected readonly ToolManager _tools;
|
||||||
|
protected readonly Func<Config> _getConfig;
|
||||||
|
protected readonly ToolStripItemCollection _raDropDownItems;
|
||||||
|
protected readonly Action _shutdownRACallback;
|
||||||
|
|
||||||
|
protected IEmulator Emu => _mainForm.Emulator;
|
||||||
|
protected IMemoryDomains Domains => Emu.AsMemoryDomains();
|
||||||
|
protected IGameInfo Game => _mainForm.Game;
|
||||||
|
protected IMovieSession MovieSession => _mainForm.MovieSession;
|
||||||
|
|
||||||
|
protected IReadOnlyList<MemFunctions> _memFunctions;
|
||||||
|
|
||||||
|
protected RetroAchievements(IMainFormForRetroAchievements mainForm, InputManager inputManager, ToolManager tools,
|
||||||
|
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
|
||||||
|
{
|
||||||
|
_mainForm = mainForm;
|
||||||
|
_inputManager = inputManager;
|
||||||
|
_tools = tools;
|
||||||
|
_getConfig = getConfig;
|
||||||
|
_raDropDownItems = raDropDownItems;
|
||||||
|
_shutdownRACallback = shutdownRACallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IRetroAchievements CreateImpl(IMainFormForRetroAchievements mainForm, InputManager inputManager, ToolManager tools,
|
||||||
|
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
|
||||||
|
{
|
||||||
|
if (getConfig().SkipRATelemetryWarning || mainForm.ShowMessageBox2(
|
||||||
|
owner: null,
|
||||||
|
text: "In order to use RetroAchievements, some information needs to be sent to retroachievements.org:\n" +
|
||||||
|
"\n\u2022 Your RetroAchievements username and password (first login) or token (subsequent logins)." +
|
||||||
|
"\n\u2022 The hash of the game(s) you have loaded into BizHawk. (for game identification + achievement unlock + leaderboard submission)" +
|
||||||
|
"\n\u2022 The RetroAchievements game ID(s) of the game(s) you have loaded into BizHawk. (for game information + achievement definitions + leaderboard definitions + rich presence definitions + code notes + achievement badges + user unlocks + leaderboard submission + ticket submission)" +
|
||||||
|
"\n\u2022 Rich presence data (periodically sent, derived from emulated game memory)." +
|
||||||
|
"\n\u2022 Whether or not you are currently in \"Hardcore Mode\" (for achievement unlock)." +
|
||||||
|
"\n\u2022 Ticket submission type and message (when submitting tickets with RAIntegration)." + // todo: add this to our impl? doesn't seem to be supported in rcheevos...
|
||||||
|
"\n\nDo you agree to send this information to retroachievements.org?",
|
||||||
|
caption: "Notice",
|
||||||
|
icon: EMsgBoxIcon.Question,
|
||||||
|
useOKCancel: false))
|
||||||
|
{
|
||||||
|
getConfig().SkipRATelemetryWarning = true;
|
||||||
|
|
||||||
|
if (RAIntegration.IsAvailable && RAIntegration.CheckUpdateRA(mainForm))
|
||||||
|
{
|
||||||
|
return new RAIntegration(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new RCheevos(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Update();
|
||||||
|
|
||||||
|
public abstract void OnFrameAdvance();
|
||||||
|
|
||||||
|
public abstract void Restart();
|
||||||
|
|
||||||
|
public abstract void Stop();
|
||||||
|
|
||||||
|
public abstract void OnSaveState(string path);
|
||||||
|
|
||||||
|
public abstract void OnLoadState(string path);
|
||||||
|
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
foreach (var tab in HotkeyInfo.Groupings)
|
foreach (var tab in HotkeyInfo.Groupings)
|
||||||
{
|
{
|
||||||
|
if (tab == "RAIntegration" && !RAIntegration.IsAvailable)
|
||||||
|
{
|
||||||
|
continue; // skip RA hotkeys if it can't be used
|
||||||
|
}
|
||||||
|
|
||||||
var tb = new TabPage { Name = tab, Text = tab };
|
var tb = new TabPage { Name = tab, Text = tab };
|
||||||
var bindings = HotkeyInfo.AllHotkeys.Where(kvp => kvp.Value.TabGroup == tab)
|
var bindings = HotkeyInfo.AllHotkeys.Where(kvp => kvp.Value.TabGroup == tab)
|
||||||
.OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName);
|
.OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName);
|
||||||
|
|
Loading…
Reference in New Issue