Skip to content

Commit

Permalink
LibSceTool initial test
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden committed Nov 3, 2024
1 parent 556954c commit a6ac53d
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 72 deletions.
10 changes: 5 additions & 5 deletions Refresher.AndroidApp/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using _Microsoft.Android.Resource.Designer;
using Android.Content;
using LibSceSharp;
using Refresher.Core.Pipelines;
using SCEToolSharp;

using ConditionalAttribute = System.Diagnostics.ConditionalAttribute;

Expand Down Expand Up @@ -48,24 +48,24 @@ private void AddButtonForPipeline<TPipeline>(LinearLayout layout, string name) w
private void AddSceToolSharpTestButton(LinearLayout layout)
{
Button button = new(this);
button.Text = "DEBUG: Test LibSceToolSharp";
button.Text = "DEBUG: Test LibSceSharp";
button.SetAllCaps(false);

button.Click += (_, _) =>
{
string initReturn;
string initReturn = "Success!";

try
{
initReturn = LibSceToolSharp.Init().ToString();
using LibSce sce = new();
}
catch (Exception ex)
{
initReturn = ex.ToString();
}

new AlertDialog.Builder(this)
.SetTitle("LibSceToolSharp.Init() returns")?
.SetTitle("LibSceSharp returns:")?
.SetMessage(initReturn)?
.SetPositiveButton("Hell yeah", (_, _) => {})?
.SetNegativeButton("FUCK", (_, _) => {})?
Expand Down
7 changes: 6 additions & 1 deletion Refresher.Core/Patching/EncryptionDetails.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
using LibSceSharp;

namespace Refresher.Core.Patching;

public class EncryptionDetails
{
public string? LicenseDirectory { get; internal set; }
public LibSce? Sce { get; internal set; }
public Self? Self { get; internal set; }

public string? DownloadedActDatPath { get; internal set; }
public string? DownloadedLicensePath { get; internal set; }
public byte[]? ConsoleIdps { get; internal set; }
}
4 changes: 2 additions & 2 deletions Refresher.Core/Pipelines/PS3PatchPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ public class PS3PatchPipeline : Pipeline
protected override List<Type> StepTypes =>
[
// Info gathering stage
typeof(PrepareSceToolStep),
typeof(ValidateGameStep),
typeof(DownloadParamSfoStep),
typeof(DownloadGameEbootStep),
typeof(ReadEbootContentIdStep),
typeof(LoadAndReadSelfStep),
typeof(DownloadGameLicenseStep),
typeof(GetConsoleIdpsStep),

// Decryption and patch stage
typeof(PrepareSceToolStep),
typeof(DecryptGameEbootStep),
typeof(PrepareEbootPatcherAndVerifyStep),
typeof(ApplyPatchToEbootStep),
Expand Down
4 changes: 2 additions & 2 deletions Refresher.Core/Pipelines/RPCS3PatchPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ public class RPCS3PatchPipeline : Pipeline
protected override List<Type> StepTypes =>
[
// Info gathering stage
typeof(PrepareSceToolStep),
typeof(ValidateGameStep),
typeof(DownloadParamSfoStep),
typeof(DownloadGameEbootStep),
typeof(ReadEbootContentIdStep),
typeof(LoadAndReadSelfStep),
typeof(DownloadGameLicenseStep),

// Decryption and patch stage
typeof(PrepareSceToolStep),
typeof(DecryptGameEbootStep),
typeof(PrepareEbootPatchCreatorAndVerifyStep),
typeof(ApplyPatchToEbootStep),
Expand Down
41 changes: 33 additions & 8 deletions Refresher.Core/Pipelines/Steps/DecryptGameEbootStep.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using SCEToolSharp;
using LibSceSharp;

namespace Refresher.Core.Pipelines.Steps;

Expand All @@ -10,16 +10,41 @@ public DecryptGameEbootStep(Pipeline pipeline) : base(pipeline)
public override float Progress { get; protected set; }
public override Task ExecuteAsync(CancellationToken cancellationToken = default)
{
LibSceToolSharp.PrintInfos(this.Game.DownloadedEbootPath!);
this.Encryption.Self?.Dispose();

LibSce sce = this.Encryption.Sce!;
byte[] selfData = File.ReadAllBytes(this.Game.DownloadedEbootPath!);

// if we don't need to decrypt, just don't. ez
if (!this.Game.ShouldUseNpdrmEncryption.GetValueOrDefault())
{
this.Encryption.Self = new Self(sce, selfData);
}
// if we downloaded an act.dat file, we downloaded a rif. use full console decryption.
if (this.Encryption.DownloadedActDatPath != null)
{
byte[] rifData = File.ReadAllBytes(this.Encryption.DownloadedLicensePath!);
byte[] actData = File.ReadAllBytes(this.Encryption.DownloadedActDatPath!);
byte[] idps = this.Encryption.ConsoleIdps!;
this.Encryption.Self = new Self(sce, selfData, rifData, actData, idps);
}
// if we still downloaded a license, we downloaded a rap file. use rap decryption.
else if(this.Encryption.DownloadedLicensePath != null)
{
byte[] rapData = File.ReadAllBytes(this.Encryption.DownloadedLicensePath!);
this.Encryption.Self = new Self(sce, selfData, rapData);
}
// otherwise, we're fucked
else
{
throw new InvalidOperationException("No encryption method could be determined.");
}

string tempFile = this.Game.DecryptedEbootPath = Path.GetTempFileName();
LibSceToolSharp.Decrypt(this.Game.DownloadedEbootPath!, tempFile);
Span<byte> elfBytes = this.Encryption.Self!.ExtractToElf();
File.WriteAllBytes(tempFile, elfBytes.ToArray());

// HACK: scetool doesn't give us result codes, check if the file has been written to instead
if (new FileInfo(tempFile).Length == 0)
{
throw new Exception($"Decryption of the EBOOT failed. Support info: game='{this.Game}' npdrm='{this.Game.ShouldUseNpdrmEncryption}'");
}
sce.FreeMemory(elfBytes);

return Task.CompletedTask;
}
Expand Down
35 changes: 11 additions & 24 deletions Refresher.Core/Pipelines/Steps/DownloadGameLicenseStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,9 @@ public override Task ExecuteAsync(CancellationToken cancellationToken = default)
{
GameInformation game = this.Game;
string? contentId = game.ContentId;

string licenseDir = Path.Join(Path.GetTempPath(), "refresher-" + Random.Shared.Next());
Directory.CreateDirectory(licenseDir);

this.Pipeline.EncryptionDetails = new EncryptionDetails()
{
LicenseDirectory = licenseDir,
};
if (contentId == null)
throw new NotImplementedException("Cannot pick a license to decrypt with without a content id");

bool found = false;
foreach (string user in this.Pipeline.Accessor!.GetDirectoriesInDirectory(Path.Combine("home")))
Expand All @@ -35,11 +30,9 @@ public override Task ExecuteAsync(CancellationToken cancellationToken = default)

foreach (string licenseFile in this.Pipeline.Accessor.GetFilesInDirectory(exdataFolder))
{
//If the license file does not contain the content ID or the title ID in its path, skip it
if (!(contentId != null && licenseFile.Contains(contentId)) && !licenseFile.Contains(game.TitleId))
// If the license file does not start with the content ID, skip it.
if (!Path.GetFileName(licenseFile).StartsWith(contentId))
continue;

State.Logger.LogDebug(Crypto, $"Found compatible rap: {licenseFile}");

string actDatPath = Path.Combine(user, "exdata", "act.dat");

Expand All @@ -52,27 +45,21 @@ public override Task ExecuteAsync(CancellationToken cancellationToken = default)

//And the license file
string downloadedLicenseFile = this.Pipeline.Accessor.DownloadFile(licenseFile);
File.Move(downloadedLicenseFile, Path.Join(licenseDir, Path.GetFileName(licenseFile)), true);

State.Logger.LogInfo(Crypto, $"Downloaded compatible license file {licenseFile}.");
this.Encryption.DownloadedLicensePath = downloadedLicenseFile;

if(Path.GetFileNameWithoutExtension(licenseFile) == game.ContentId)
found = true;
State.Logger.LogInfo(Crypto, $"Downloaded license file '{licenseFile}'.");
found = true;
break;
}

if (found)
break;
}

if (!found)
{
this.Game.ShouldUseNpdrmEncryption = false;
State.Logger.LogWarning(Crypto, "Couldn't find a license file for {0}. For disc copies, this is normal. " +
"For digital copies, this may present problems. Attempting to continue without it...", game.TitleId);
}
else
if (!found && this.Game.ShouldUseNpdrmEncryption.GetValueOrDefault())
{
this.Game.ShouldUseNpdrmEncryption = true;
State.Logger.LogWarning(Crypto, "Couldn't find a license file for {0}. " +
"This may present problems. Attempting to continue without it...", game.TitleId);
}

return Task.CompletedTask;
Expand Down
12 changes: 5 additions & 7 deletions Refresher.Core/Pipelines/Steps/EncryptGameEbootStep.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using SCEToolSharp;

namespace Refresher.Core.Pipelines.Steps;
namespace Refresher.Core.Pipelines.Steps;

public class EncryptGameEbootStep : Step
{
Expand All @@ -13,17 +11,17 @@ public override Task ExecuteAsync(CancellationToken cancellationToken = default)
if (this.Game.ShouldUseNpdrmEncryption ?? this.Game.TitleId.StartsWith('N'))
{
State.Logger.LogDebug(Crypto, "Will encrypt using Npdrm");
LibSceToolSharp.SetNpdrmEncryptOptions();
LibSceToolSharp.SetNpdrmContentId(this.Game.ContentId!);
// TODO: LibSceToolSharp.SetNpdrmEncryptOptions();
// TODO: LibSceToolSharp.SetNpdrmContentId(this.Game.ContentId!);
}
else
{
State.Logger.LogDebug(Crypto, "Will encrypt using Disc");
LibSceToolSharp.SetDiscEncryptOptions();
// TODO: LibSceToolSharp.SetDiscEncryptOptions();
}

string tempFile = this.Game.EncryptedEbootPath = Path.GetTempFileName();
LibSceToolSharp.Encrypt(this.Game.DecryptedEbootPath!, tempFile);
// TODO: LibSceToolSharp.Encrypt(this.Game.DecryptedEbootPath!, tempFile);

// HACK: scetool doesn't give us result codes, check if the file has been written to instead
if (new FileInfo(tempFile).Length == 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
using SCEToolSharp;
using LibSceSharp;

namespace Refresher.Core.Pipelines.Steps;

public class ReadEbootContentIdStep : Step
public class LoadAndReadSelfStep : Step
{
public ReadEbootContentIdStep(Pipeline pipeline) : base(pipeline)
{}
public LoadAndReadSelfStep(Pipeline pipeline) : base(pipeline)
{
}

public override float Progress { get; protected set; }
public override Task ExecuteAsync(CancellationToken cancellationToken = default)
{
string ebootPath = this.Game.DownloadedEbootPath!;
Self self = new(this.Encryption.Sce!, File.ReadAllBytes(this.Game.DownloadedEbootPath!), true);
this.Encryption.Self = self;

LibSceToolSharp.Init();
this.Progress = 0.5f;

string? contentId = LibSceToolSharp.GetContentId(ebootPath)?.TrimEnd('\0');
this.Game.ContentId = contentId;

this.Progress = 1f;
string? contentId = this.Encryption.Self!.ContentId;

if(contentId != null)
State.Logger.LogDebug(InfoRetrieval, "Got content ID from the game's EBOOT: {0}", contentId);
else
State.Logger.LogDebug(InfoRetrieval, "Unable to find content ID in the game's EBOOT.");

this.Game.ContentId = contentId;
this.Game.ShouldUseNpdrmEncryption = self.NeedsNpdrmLicense;

return Task.CompletedTask;
}
}
15 changes: 5 additions & 10 deletions Refresher.Core/Pipelines/Steps/PrepareSceToolStep.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using static SCEToolSharp.LibSceToolSharp;
using LibSceSharp;
using Refresher.Core.Patching;

namespace Refresher.Core.Pipelines.Steps;

Expand All @@ -11,16 +12,10 @@ public PrepareSceToolStep(Pipeline pipeline) : base(pipeline)
public override float Progress { get; protected set; }
public override Task ExecuteAsync(CancellationToken cancellationToken = default)
{
Init();

SetRapDirectory(this.Encryption.LicenseDirectory!);
SetRifPath(this.Encryption.LicenseDirectory!);

if(this.Encryption.DownloadedActDatPath != null)
SetActDatFilePath(this.Encryption.DownloadedActDatPath);
this.Pipeline.EncryptionDetails = new EncryptionDetails();

if(this.Encryption.ConsoleIdps != null)
SetIdpsKey(this.Encryption.ConsoleIdps);
LibSce sce = new();
this.Encryption.Sce = sce;

return Task.CompletedTask;
}
Expand Down
4 changes: 3 additions & 1 deletion Refresher.Core/Refresher.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DebugType>embedded</DebugType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentFTP" Version="49.0.2" />
<PackageReference Include="NotEnoughLogs" Version="2.0.3" />
<PackageReference Include="SCEToolSharp" Version="1.2.3" />
<PackageReference Include="LibSceSharp" Version="1.0.0" />
<PackageReference Include="LibSceSharp.Native" Version="1.0.0" />
<PackageReference Include="ELFSharp" Version="2.17.3" />
<PackageReference Include="Sentry" Version="4.4.0" />
</ItemGroup>
Expand Down
26 changes: 26 additions & 0 deletions Refresher.Core/State.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibSceSharp;
using NotEnoughLogs;
using NotEnoughLogs.Behaviour;
using NotEnoughLogs.Sinks;
Expand All @@ -12,6 +15,24 @@ public static class State

public delegate void RefresherLogHandler(RefresherLog log);
public static event RefresherLogHandler? Log;

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void SceLog(byte* scopePtr, LibSceLogLevel nativeLevel, byte* messagePtr)
{
string? scope = Marshal.PtrToStringUTF8((IntPtr)scopePtr);
string? message = Marshal.PtrToStringUTF8((IntPtr)messagePtr);

LogLevel level = nativeLevel switch
{
LibSceLogLevel.Error => LogLevel.Error,
LibSceLogLevel.Warning => LogLevel.Warning,
LibSceLogLevel.Info => LogLevel.Info,
LibSceLogLevel.Debug => LogLevel.Debug,
_ => throw new ArgumentOutOfRangeException(nameof(nativeLevel), nativeLevel, null),
};

Logger.Log(level, $"libsce/{scope}", message);
}

public static void InitializeLogger(IEnumerable<ILoggerSink> sinks)
{
Expand All @@ -23,6 +44,11 @@ public static void InitializeLogger(IEnumerable<ILoggerSink> sinks)
Behaviour = new DirectLoggingBehaviour(),
MaxLevel = LogLevel.Trace,
});

unsafe
{
LibSce.SetLogCallback(&SceLog);
}
}

internal static void InvokeOnLog(LogLevel level, ReadOnlySpan<char> category, ReadOnlySpan<char> content)
Expand Down

0 comments on commit a6ac53d

Please sign in to comment.