several rcheevo updates, mostly for 3DS stuff
This commit is contained in:
parent
67b535d0f8
commit
d03d076fa7
Binary file not shown.
|
@ -11,22 +11,13 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
|||
set(CMAKE_C_EXTENSIONS ON)
|
||||
|
||||
if(MSVC)
|
||||
# MSVC targets don't export all symbols unless this is on
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
# max warnings, treat as errors
|
||||
add_compile_options(/W4)
|
||||
add_compile_options(/WX)
|
||||
|
||||
# ignore some warnings
|
||||
# this differs between clang-cl and cl
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
# ignore some warnings on cl
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||
add_compile_options(
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
else()
|
||||
add_compile_options(
|
||||
/wd4100 # "unreferenced formal parameter"
|
||||
/wd4244 # "conversion from 'type1' to 'type2', possible loss of data"
|
||||
/wd4245 # "conversion from 'type1' to 'type2', signed/unsigned mismatch"
|
||||
)
|
||||
|
@ -47,12 +38,6 @@ else()
|
|||
add_compile_options(-Wall -Wextra)
|
||||
add_compile_options(-Werror)
|
||||
|
||||
# ignore some warnings
|
||||
add_compile_options(
|
||||
-Wno-unused-parameter
|
||||
-Wno-implicit-fallthrough
|
||||
)
|
||||
|
||||
# strip in release, optimize for gdb usage in debug
|
||||
add_link_options($<$<CONFIG:RELEASE>:-s>)
|
||||
add_compile_options($<$<CONFIG:DEBUG>:-ggdb>)
|
||||
|
@ -75,7 +60,7 @@ add_library(
|
|||
${RC_SRC_DIR}/rc_compat.c
|
||||
${RC_SRC_DIR}/rc_compat.h
|
||||
${RC_SRC_DIR}/rc_util.c
|
||||
${RC_SRC_DIR}/rc_util.h
|
||||
${RC_SRC_DIR}/rc_version.c
|
||||
${RC_SRC_DIR}/rc_version.h
|
||||
${RC_SRC_DIR}/rapi/rc_api_common.c
|
||||
${RC_SRC_DIR}/rapi/rc_api_common.h
|
||||
|
@ -99,6 +84,8 @@ add_library(
|
|||
${RC_SRC_DIR}/rcheevos/runtime_progress.c
|
||||
${RC_SRC_DIR}/rcheevos/trigger.c
|
||||
${RC_SRC_DIR}/rcheevos/value.c
|
||||
${RC_SRC_DIR}/rhash/aes.c
|
||||
${RC_SRC_DIR}/rhash/aes.h
|
||||
${RC_SRC_DIR}/rhash/cdreader.c
|
||||
${RC_SRC_DIR}/rhash/hash.c
|
||||
${RC_SRC_DIR}/rhash/md5.c
|
||||
|
@ -110,13 +97,15 @@ add_library(
|
|||
${RC_INCLUDE_DIR}/rc_api_user.h
|
||||
${RC_INCLUDE_DIR}/rc_consoles.h
|
||||
${RC_INCLUDE_DIR}/rc_error.h
|
||||
${RC_INCLUDE_DIR}/rc_export.h
|
||||
${RC_INCLUDE_DIR}/rc_hash.h
|
||||
${RC_INCLUDE_DIR}/rc_runtime.h
|
||||
${RC_INCLUDE_DIR}/rc_runtime_types.h
|
||||
${RC_INCLUDE_DIR}/rc_util.h
|
||||
${RC_INCLUDE_DIR}/rcheevos.h
|
||||
)
|
||||
|
||||
target_compile_definitions(${RC_TARGET} PRIVATE RC_DISABLE_LUA RC_NO_THREADS)
|
||||
target_compile_definitions(${RC_TARGET} PRIVATE RC_DISABLE_LUA RC_NO_THREADS RC_SHARED)
|
||||
target_include_directories(${RC_TARGET} PRIVATE ${RC_INCLUDE_DIR})
|
||||
|
||||
add_custom_command(
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6fb3ebca22fe4f3a97e7a391e5e9c4623aa2286a
|
||||
Subproject commit e7989c300280ba06d7621ae5b4e00ac7fe28d97a
|
|
@ -17,6 +17,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
IMovieSession MovieSession { get; }
|
||||
|
||||
FirmwareManager FirmwareManager { get; }
|
||||
|
||||
event BeforeQuickLoadEventHandler QuicksaveLoad;
|
||||
|
||||
SettingsAdapter GetSettingsAdapterForLoadedCoreUntyped();
|
||||
|
|
|
@ -90,6 +90,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL = 5,
|
||||
}
|
||||
|
||||
public enum rc_runtime_achievement_type_t : uint
|
||||
{
|
||||
RC_ACHIEVEMENT_TYPE_STANDARD = 0,
|
||||
RC_ACHIEVEMENT_TYPE_MISSABLE = 1,
|
||||
RC_ACHIEVEMENT_TYPE_PROGRESSION = 2,
|
||||
RC_ACHIEVEMENT_TYPE_WIN = 3,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct rc_runtime_event_t
|
||||
{
|
||||
|
@ -163,13 +171,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly record struct rc_api_start_session_request_t(string username, string api_token, uint game_id)
|
||||
public readonly record struct rc_api_start_session_request_t(string username, string api_token, uint game_id, string game_hash, bool hardcore)
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)]
|
||||
public readonly string username = username;
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)]
|
||||
public readonly string api_token = api_token;
|
||||
public readonly uint game_id = game_id;
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)]
|
||||
public readonly string game_hash = game_hash;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public readonly bool hardcore = hardcore;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -182,7 +194,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly record struct rc_api_achievement_definition_t(uint id, uint points, rc_runtime_achievement_category_t category,
|
||||
IntPtr title, IntPtr description, IntPtr definition, IntPtr author, IntPtr badge_name, long created, long updated)
|
||||
IntPtr title, IntPtr description, IntPtr definition, IntPtr author, IntPtr badge_name, long created, long updated,
|
||||
rc_runtime_achievement_type_t type, float rarity, float rarity_hardcore)
|
||||
{
|
||||
public string Title => Mershul.PtrToStringUtf8(title);
|
||||
public string Description => Mershul.PtrToStringUtf8(description);
|
||||
|
@ -252,7 +265,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly record struct rc_api_ping_request_t(string username, string api_token, uint game_id, string rich_presence)
|
||||
public readonly record struct rc_api_ping_request_t(string username, string api_token, uint game_id, string rich_presence, string game_hash, bool hardcore)
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)]
|
||||
public readonly string username = username;
|
||||
|
@ -260,7 +273,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
public readonly string api_token = api_token;
|
||||
public readonly uint game_id = game_id;
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)]
|
||||
public readonly string rich_presence = rich_presence;
|
||||
public readonly string game_hash = game_hash;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public readonly bool hardcore = hardcore;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -344,6 +359,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
[UnmanagedFunctionPointer(cc)]
|
||||
public delegate void rc_hash_message_callback([MarshalAs(UnmanagedType.LPUTF8Str)] string message);
|
||||
|
||||
[UnmanagedFunctionPointer(cc)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public delegate bool rc_hash_3ds_get_cia_normal_key_func(byte common_key_index, IntPtr out_normal_key);
|
||||
|
||||
[UnmanagedFunctionPointer(cc)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public delegate bool rc_hash_3ds_get_ncch_normal_keys_func(IntPtr primary_key_y, byte secondary_key_x_slot, IntPtr optional_program_id, IntPtr out_primary_key, IntPtr out_secondary_key);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract IntPtr rc_version_string();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract IntPtr rc_error_str(rc_error_t error_code);
|
||||
|
||||
|
@ -404,6 +430,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
[BizImport(cc, Compatibility = true)]
|
||||
public abstract void rc_hash_init_custom_filereader(in rc_hash_filereader reader);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func);
|
||||
|
||||
[BizImport(cc)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public abstract bool rc_hash_generate_from_buffer(byte[] hash, RetroAchievements.ConsoleID console_id, byte[] buffer, nuint buffer_size);
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
_resolver = new("RA_Integration-x64.dll", hasLimitedLifetime: true);
|
||||
_RA = BizInvoker.GetInvoker<RAInterface>(_resolver, DummyMonitor.Singleton, CallingConventionAdapters.Native);
|
||||
_version = new(Marshal.PtrToStringAnsi(_RA.IntegrationVersion())!);
|
||||
Console.WriteLine($"Loaded RetroAchievements v{_version}");
|
||||
Console.WriteLine($"Loaded RAIntegration v{_version}");
|
||||
}
|
||||
|
||||
private static void DetachDll()
|
||||
|
|
|
@ -55,6 +55,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public DateTime Created { get; }
|
||||
public DateTime Updated { get; }
|
||||
public LibRCheevos.rc_runtime_achievement_type_t Type { get; }
|
||||
public float Rarity { get; }
|
||||
public float RarityHardcore { get; }
|
||||
|
||||
public bool IsSoftcoreUnlocked { get; set; }
|
||||
public bool IsHardcoreUnlocked { get; set; }
|
||||
|
@ -100,6 +103,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
BadgeName = cheevo.BadgeName;
|
||||
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();
|
||||
Type = cheevo.type;
|
||||
Rarity = cheevo.rarity;
|
||||
RarityHardcore = cheevo.rarity_hardcore;
|
||||
IsSoftcoreUnlocked = false;
|
||||
IsHardcoreUnlocked = false;
|
||||
IsPrimed = false;
|
||||
|
@ -119,6 +125,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
BadgeName = cheevo.BadgeName;
|
||||
Created = cheevo.Created;
|
||||
Updated = cheevo.Updated;
|
||||
Type = cheevo.Type;
|
||||
Rarity = cheevo.Rarity;
|
||||
RarityHardcore = cheevo.RarityHardcore;
|
||||
IsSoftcoreUnlocked = false;
|
||||
IsHardcoreUnlocked = false;
|
||||
IsPrimed = false;
|
||||
|
|
|
@ -12,9 +12,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
private readonly LibRCheevos.rc_api_start_session_request_t _apiParams;
|
||||
|
||||
public StartGameSessionRequest(string username, string apiToken, uint gameId)
|
||||
public StartGameSessionRequest(string username, string apiToken, uint gameId, string gameHash, bool hardcore)
|
||||
{
|
||||
_apiParams = new(username, apiToken, gameId);
|
||||
_apiParams = new(username, apiToken, gameId, gameHash, hardcore);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
|
@ -35,17 +35,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
private void StartGameSession()
|
||||
{
|
||||
PushRequest(new StartGameSessionRequest(Username, ApiToken, _gameData.GameID));
|
||||
}
|
||||
=> PushRequest(new StartGameSessionRequest(Username, ApiToken, _gameData.GameID, _gameHash, HardcoreMode));
|
||||
|
||||
private sealed class PingRequest : RCheevoHttpRequest
|
||||
{
|
||||
private readonly LibRCheevos.rc_api_ping_request_t _apiParams;
|
||||
|
||||
public PingRequest(string username, string apiToken, uint gameId, string richPresence)
|
||||
public PingRequest(string username, string apiToken, uint gameId, string richPresence, string gameHash, bool hardcore)
|
||||
{
|
||||
_apiParams = new(username, apiToken, gameId, richPresence);
|
||||
_apiParams = new(username, apiToken, gameId, richPresence, gameHash, hardcore);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
|
@ -66,9 +64,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
private void SendPing()
|
||||
{
|
||||
PushRequest(new PingRequest(Username, ApiToken, _gameData.GameID, CurrentRichPresence));
|
||||
}
|
||||
=> PushRequest(new PingRequest(Username, ApiToken, _gameData.GameID, CurrentRichPresence, _gameHash, HardcoreMode));
|
||||
|
||||
private readonly byte[] _richPresenceBuffer = new byte[1024];
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
@ -22,6 +23,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
OSTailoredCode.IsUnixHost ? "librcheevos.so" : "librcheevos.dll", hasLimitedLifetime: false);
|
||||
_lib = BizInvoker.GetInvoker<LibRCheevos>(resolver, CallingConventionAdapters.Native);
|
||||
|
||||
var version = Marshal.PtrToStringAnsi(_lib.rc_version_string());
|
||||
Console.WriteLine($"Loaded RCheevos v{version}");
|
||||
|
||||
// init message callbacks
|
||||
_errorMessageCallback = ErrorMessageCallback;
|
||||
_verboseMessageCallback = VerboseMessageCallback;
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Client.Common;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.DiscSystem;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
|
@ -192,7 +197,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
(buf2352[j + bootLenOffset + 2] << 8) | buf2352[j + bootLenOffset + 3];
|
||||
bootLba = startLba + i;
|
||||
bootOff = j + bootLenOffset + 4;
|
||||
byteswapped = false;
|
||||
// byteswapped = false;
|
||||
foundHeader = true;
|
||||
break;
|
||||
}
|
||||
|
@ -284,6 +289,184 @@ namespace BizHawk.Client.EmuHawk
|
|||
return IdentifyHash(hash);
|
||||
}
|
||||
|
||||
// Stuff needed for 3DS hashing...
|
||||
private readonly LibRCheevos.rc_hash_3ds_get_cia_normal_key_func _getCiaNormalKeyFunc;
|
||||
private readonly LibRCheevos.rc_hash_3ds_get_ncch_normal_keys_func _getNcchNormalKeysFunc;
|
||||
// https://github.com/citra-emu/citra/blob/2b20082581906fe973e26ed36bef695aa1f64527/src/core/hw/aes/key.cpp#L23-L30
|
||||
private static readonly BigInteger GENERATOR_CONSTANT = BigInteger.Parse("1FF9E9AAC5FE0408024591DC5D52768A", NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
private static readonly BigInteger U128_MAX = BigInteger.Parse("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
|
||||
private static byte[] Derive3DSNormalKey(BigInteger keyX, BigInteger keyY)
|
||||
{
|
||||
static BigInteger LeftRot128(BigInteger v, int rot)
|
||||
{
|
||||
var l = (v << rot) & U128_MAX;
|
||||
var r = v >> (128 - rot);
|
||||
return l | r;
|
||||
}
|
||||
|
||||
static BigInteger Add128(BigInteger v1, BigInteger v2)
|
||||
=> (v1 + v2) & U128_MAX;
|
||||
|
||||
var normalKey = LeftRot128(Add128(LeftRot128(keyX, 2) ^ keyY, GENERATOR_CONSTANT), 87);
|
||||
var normalKeyBytes = normalKey.ToByteArray();
|
||||
if (normalKeyBytes.Length > 17)
|
||||
{
|
||||
// this shoudn't ever happen
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// get rid of a final trailing 0
|
||||
// but also make sure we have 0 paddng to 16 bytes
|
||||
Array.Resize(ref normalKeyBytes, 16);
|
||||
|
||||
// .ToByteArray() is always in little endian order, but we want big endian order
|
||||
Array.Reverse(normalKeyBytes);
|
||||
return normalKeyBytes;
|
||||
}
|
||||
|
||||
private MemoryStream GetFirmware(FirmwareID id)
|
||||
{
|
||||
var record = FirmwareDatabase.FirmwareRecords.First(fr => fr.ID == id);
|
||||
var resolved = _mainForm.FirmwareManager.Resolve(_getConfig().PathEntries, _getConfig().FirmwareUserSpecifications, record);
|
||||
if (resolved?.FilePath == null) throw new InvalidOperationException();
|
||||
return new(File.ReadAllBytes(resolved.FilePath), writable: false);
|
||||
}
|
||||
|
||||
private (BigInteger Key1, BigInteger Key2) FindAesKeys(string key1Prefix, string key2Prefix)
|
||||
{
|
||||
using var keys = new StreamReader(GetFirmware(new("3DS", "aes_keys")), Encoding.UTF8);
|
||||
string key1Str = null, key2Str = null;
|
||||
while ((key1Str is null || key2Str is null) && keys.ReadLine() is { } line)
|
||||
{
|
||||
if (line.Length == 0 || line.StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eqpos = line.IndexOf('=');
|
||||
if (eqpos == -1 || eqpos != line.LastIndexOf('='))
|
||||
{
|
||||
throw new InvalidOperationException("Malformed key list");
|
||||
}
|
||||
|
||||
if (key1Str is null)
|
||||
{
|
||||
if (line.StartsWithOrdinal(key1Prefix))
|
||||
{
|
||||
key1Str = line[(eqpos + 1)..];
|
||||
if (key1Str.Length != 32)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid key length");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key2Str is null)
|
||||
{
|
||||
if (line.StartsWithOrdinal(key2Prefix))
|
||||
{
|
||||
key2Str = line[(eqpos + 1)..];
|
||||
if (key2Str.Length != 32)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid key length");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key1Str is null || key2Str is null)
|
||||
{
|
||||
throw new InvalidOperationException("Couldn't find requested keys");
|
||||
}
|
||||
|
||||
var key1 = BigInteger.Parse($"0{key1Str}", NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var key2 = BigInteger.Parse($"0{key2Str}", NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
return (key1, key2);
|
||||
}
|
||||
|
||||
private bool GetCiaNormalKeyFunc(byte common_key_index, IntPtr out_normal_key)
|
||||
{
|
||||
if (common_key_index > 5)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var (keyX, keyY) = FindAesKeys("slot0x3DKeyX=", $"common{common_key_index}=");
|
||||
Marshal.Copy(Derive3DSNormalKey(keyX, keyY), 0, out_normal_key, 16);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetNcchNormalKeysFunc(IntPtr primary_key_y, byte secondary_key_x_slot, IntPtr optional_program_id, IntPtr out_primary_key, IntPtr out_secondary_key)
|
||||
{
|
||||
if (secondary_key_x_slot is not (0x2C or 0x25 or 0x18 or 0x1B))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var (primaryKeyX, secondaryKeyX) = FindAesKeys("slot0x2CKeyX=", $"slot0x{secondary_key_x_slot:X2}KeyX=");
|
||||
|
||||
var primaryKeyYBytes = new byte[17];
|
||||
Marshal.Copy(primary_key_y, primaryKeyYBytes, 1, 16);
|
||||
Array.Reverse(primaryKeyYBytes); // convert big endian to little endian
|
||||
var primaryKeyY = new BigInteger(primaryKeyYBytes);
|
||||
|
||||
Marshal.Copy(Derive3DSNormalKey(primaryKeyX, primaryKeyY), 0, out_primary_key, 16);
|
||||
|
||||
if (optional_program_id == IntPtr.Zero)
|
||||
{
|
||||
Marshal.Copy(Derive3DSNormalKey(secondaryKeyX, primaryKeyY), 0, out_secondary_key, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
var programIdBytes = new byte[8];
|
||||
Marshal.Copy(optional_program_id, programIdBytes, 0, 8);
|
||||
var programId = BitConverter.ToUInt64(programIdBytes, 0);
|
||||
|
||||
using var seeddb = new BinaryReader(GetFirmware(new("3DS", "seeddb")));
|
||||
var count = seeddb.ReadUInt32();
|
||||
seeddb.BaseStream.Seek(12, SeekOrigin.Current); // apparently some padding bytes before actual seeds
|
||||
for (long i = 0; i < count; i++)
|
||||
{
|
||||
var titleId = seeddb.ReadUInt64();
|
||||
if (titleId != programId)
|
||||
{
|
||||
seeddb.BaseStream.Seek(24, SeekOrigin.Current);
|
||||
continue;
|
||||
}
|
||||
|
||||
var sha256Input = new byte[32];
|
||||
Marshal.Copy(primary_key_y, sha256Input, 0, 16);
|
||||
seeddb.BaseStream.Read(sha256Input, 16, 16);
|
||||
var sha256Digest = SHA256Checksum.Compute(sha256Input);
|
||||
|
||||
var secondaryKeyYBytes = new byte[17];
|
||||
Buffer.BlockCopy(sha256Digest, 0, secondaryKeyYBytes, 1, 16);
|
||||
Array.Reverse(secondaryKeyYBytes); // convert big endian to little endian
|
||||
var secondaryKeyY = new BigInteger(secondaryKeyYBytes);
|
||||
Marshal.Copy(Derive3DSNormalKey(secondaryKeyX, secondaryKeyY), 0, out_secondary_key, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private uint Hash3DS(string path)
|
||||
{
|
||||
// 3DS is too big to hash as a byte array...
|
||||
|
|
|
@ -362,17 +362,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
// these consoles will use the entire main memory domain
|
||||
private static readonly ConsoleID[] UseFullMainMem =
|
||||
{
|
||||
ConsoleID.PlayStation, ConsoleID.Lynx, ConsoleID.Lynx, ConsoleID.NeoGeoPocket,
|
||||
ConsoleID.Jaguar, ConsoleID.JaguarCD, ConsoleID.DS, ConsoleID.DSi,
|
||||
ConsoleID.AppleII, ConsoleID.Vectrex, ConsoleID.Tic80, ConsoleID.PCEngine,
|
||||
ConsoleID.Uzebox, ConsoleID.Nintendo3DS,
|
||||
ConsoleID.PlayStation, ConsoleID.Lynx, ConsoleID.NeoGeoPocket, ConsoleID.Jaguar,
|
||||
ConsoleID.JaguarCD, ConsoleID.DS, ConsoleID.DSi, ConsoleID.AppleII,
|
||||
ConsoleID.Vectrex, ConsoleID.Tic80, ConsoleID.PCEngine, ConsoleID.Uzebox,
|
||||
ConsoleID.Nintendo3DS,
|
||||
};
|
||||
|
||||
// these consoles will use part of the system bus at an offset
|
||||
private static readonly Dictionary<ConsoleID, (uint Start, uint Size)[]> UsePartialSysBus = new()
|
||||
{
|
||||
[ConsoleID.MasterSystem] = new[] { (0xC000u, 0x2000u) },
|
||||
[ConsoleID.GameGear] = new[] { (0xC000u, 0x2000u) },
|
||||
[ConsoleID.Colecovision] = new[] { (0x6000u, 0x400u) },
|
||||
[ConsoleID.SG1000] = new[] { (0xC000u, 0x2000u), (0x2000u, 0x2000u), (0x8000u, 0x2000u) },
|
||||
};
|
||||
|
@ -426,6 +424,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
// our picodrive doesn't byteswap its SRAM, so...
|
||||
TryAddDomain("SRAM", addressMangler: domains["SRAM"] is MemoryDomainIntPtrSwap16Monitor ? 1u : 0u);
|
||||
break;
|
||||
case ConsoleID.MasterSystem:
|
||||
case ConsoleID.GameGear:
|
||||
mfs.Add(new(domains["Main RAM"], 0, domains["Main RAM"].Size));
|
||||
TryAddDomain("Cart (Volatile) RAM");
|
||||
TryAddDomain("Save RAM");
|
||||
break;
|
||||
case ConsoleID.SNES:
|
||||
mfs.Add(new(domains["WRAM"], 0, domains["WRAM"].Size));
|
||||
TryAddDomain("CARTRAM");
|
||||
|
|
|
@ -41,6 +41,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
_getConfig = getConfig;
|
||||
_raDropDownItems = raDropDownItems;
|
||||
_shutdownRACallback = shutdownRACallback;
|
||||
|
||||
_getCiaNormalKeyFunc = GetCiaNormalKeyFunc;
|
||||
_getNcchNormalKeysFunc = GetNcchNormalKeysFunc;
|
||||
RCheevos._lib.rc_hash_init_3ds_get_cia_normal_key_func(_getCiaNormalKeyFunc);
|
||||
RCheevos._lib.rc_hash_init_3ds_get_ncch_normal_keys_func(_getNcchNormalKeysFunc);
|
||||
}
|
||||
|
||||
public static IRetroAchievements CreateImpl(
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.N3DS
|
|||
{
|
||||
var message = new byte[1024];
|
||||
var res = _core.Citra_InstallCIA(_context, romPath, message, message.Length);
|
||||
var outMsg = Encoding.UTF8.GetString(message).TrimEnd();
|
||||
var outMsg = Encoding.UTF8.GetString(message).TrimEnd('\0');
|
||||
if (res)
|
||||
{
|
||||
romPath = outMsg;
|
||||
|
|
Loading…
Reference in New Issue