diff --git a/LICENSE.txt b/LICENSE.txt
index 00d2e135a7..818ddd7605 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,24 +1,9 @@
-This is free and unencumbered software released into the public domain.
+MIT License
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
+Copyright (c) Ryujinx Team and Contributors
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-For more information, please refer to
\ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 426ee40a20..be3d1fea18 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
- **System Titles**
- Some of our System Modules implementation require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives).
+ Some of our System Modules implementation (like time) require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives).
You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copy the content in `RyuFs/nand/system`.
- **Executables**
diff --git a/Ryujinx.Audio/IAalOutput.cs b/Ryujinx.Audio/IAalOutput.cs
index 119fc2342d..489f90028e 100644
--- a/Ryujinx.Audio/IAalOutput.cs
+++ b/Ryujinx.Audio/IAalOutput.cs
@@ -15,8 +15,13 @@ namespace Ryujinx.Audio
void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct;
void Start(int trackId);
+
void Stop(int trackId);
+ float GetVolume();
+
+ void SetVolume(float volume);
+
PlaybackState GetState(int trackId);
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs
index 56ae5d4f5f..10943ae62b 100644
--- a/Ryujinx.Audio/Renderers/DummyAudioOut.cs
+++ b/Ryujinx.Audio/Renderers/DummyAudioOut.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.Audio
@@ -8,17 +9,18 @@ namespace Ryujinx.Audio
///
public class DummyAudioOut : IAalOutput
{
- private int lastTrackId = 1;
+ private int _lastTrackId = 1;
+ private float _volume = 1.0f;
- private ConcurrentQueue m_TrackIds;
- private ConcurrentQueue m_Buffers;
- private ConcurrentDictionary m_ReleaseCallbacks;
+ private ConcurrentQueue _trackIds;
+ private ConcurrentQueue _buffers;
+ private ConcurrentDictionary _releaseCallbacks;
public DummyAudioOut()
{
- m_Buffers = new ConcurrentQueue();
- m_TrackIds = new ConcurrentQueue();
- m_ReleaseCallbacks = new ConcurrentDictionary();
+ _buffers = new ConcurrentQueue();
+ _trackIds = new ConcurrentQueue();
+ _releaseCallbacks = new ConcurrentDictionary();
}
///
@@ -30,38 +32,23 @@ namespace Ryujinx.Audio
public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback)
{
- int trackId;
-
- if (!m_TrackIds.TryDequeue(out trackId))
+ if (!_trackIds.TryDequeue(out int trackId))
{
- trackId = ++lastTrackId;
+ trackId = ++_lastTrackId;
}
- m_ReleaseCallbacks[trackId] = callback;
+ _releaseCallbacks[trackId] = callback;
return trackId;
}
public void CloseTrack(int trackId)
{
- m_TrackIds.Enqueue(trackId);
- m_ReleaseCallbacks.Remove(trackId, out _);
+ _trackIds.Enqueue(trackId);
+ _releaseCallbacks.Remove(trackId, out _);
}
- public void Start(int trackId) { }
-
- public void Stop(int trackId) { }
-
- public void AppendBuffer(int trackID, long bufferTag, T[] buffer)
- where T : struct
- {
- m_Buffers.Enqueue(bufferTag);
-
- if (m_ReleaseCallbacks.TryGetValue(trackID, out var callback))
- {
- callback?.Invoke();
- }
- }
+ public bool ContainsBuffer(int trackID, long bufferTag) => false;
public long[] GetReleasedBuffers(int trackId, int maxCount)
{
@@ -69,7 +56,7 @@ namespace Ryujinx.Audio
for (int i = 0; i < maxCount; i++)
{
- if (!m_Buffers.TryDequeue(out long tag))
+ if (!_buffers.TryDequeue(out long tag))
{
break;
}
@@ -80,11 +67,30 @@ namespace Ryujinx.Audio
return bufferTags.ToArray();
}
- public bool ContainsBuffer(int trackID, long bufferTag) => false;
+ public void AppendBuffer(int trackID, long bufferTag, T[] buffer) where T : struct
+ {
+ _buffers.Enqueue(bufferTag);
+
+ if (_releaseCallbacks.TryGetValue(trackID, out var callback))
+ {
+ callback?.Invoke();
+ }
+ }
+
+ public void Start(int trackId) { }
+
+ public void Stop(int trackId) { }
+
+ public float GetVolume() => _volume;
+
+ public void SetVolume(float volume)
+ {
+ _volume = volume;
+ }
public void Dispose()
{
- m_Buffers.Clear();
+ _buffers.Clear();
}
}
-}
+}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs
index 6e085eed26..69f36a4daf 100644
--- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs
+++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs
@@ -2,7 +2,6 @@ using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
@@ -13,174 +12,43 @@ namespace Ryujinx.Audio
///
public class OpenALAudioOut : IAalOutput, IDisposable
{
+ ///
+ /// The maximum amount of tracks we can issue simultaneously
+ ///
private const int MaxTracks = 256;
- private const int MaxReleased = 32;
-
- private AudioContext Context;
-
- private class Track : IDisposable
- {
- public int SourceId { get; private set; }
-
- public int SampleRate { get; private set; }
-
- public ALFormat Format { get; private set; }
-
- private ReleaseCallback Callback;
-
- public PlaybackState State { get; set; }
-
- private ConcurrentDictionary Buffers;
-
- private Queue QueuedTagsQueue;
-
- private Queue ReleasedTagsQueue;
-
- private bool Disposed;
-
- public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback)
- {
- this.SampleRate = SampleRate;
- this.Format = Format;
- this.Callback = Callback;
-
- State = PlaybackState.Stopped;
-
- SourceId = AL.GenSource();
-
- Buffers = new ConcurrentDictionary();
-
- QueuedTagsQueue = new Queue();
-
- ReleasedTagsQueue = new Queue();
- }
-
- public bool ContainsBuffer(long Tag)
- {
- foreach (long QueuedTag in QueuedTagsQueue)
- {
- if (QueuedTag == Tag)
- {
- return true;
- }
- }
-
- return false;
- }
-
- public long[] GetReleasedBuffers(int Count)
- {
- AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount);
-
- ReleasedCount += ReleasedTagsQueue.Count;
-
- if (Count > ReleasedCount)
- {
- Count = ReleasedCount;
- }
-
- List Tags = new List();
-
- while (Count-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag))
- {
- Tags.Add(Tag);
- }
-
- while (Count-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag))
- {
- AL.SourceUnqueueBuffers(SourceId, 1);
-
- Tags.Add(Tag);
- }
-
- return Tags.ToArray();
- }
-
- public int AppendBuffer(long Tag)
- {
- if (Disposed)
- {
- throw new ObjectDisposedException(nameof(Track));
- }
-
- int Id = AL.GenBuffer();
-
- Buffers.AddOrUpdate(Tag, Id, (Key, OldId) =>
- {
- AL.DeleteBuffer(OldId);
-
- return Id;
- });
-
- QueuedTagsQueue.Enqueue(Tag);
-
- return Id;
- }
-
- public void CallReleaseCallbackIfNeeded()
- {
- AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount);
-
- if (ReleasedCount > 0)
- {
- // If we signal, then we also need to have released buffers available
- // to return when GetReleasedBuffers is called.
- // If playback needs to be re-started due to all buffers being processed,
- // then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue.
- while (ReleasedCount-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag))
- {
- AL.SourceUnqueueBuffers(SourceId, 1);
-
- ReleasedTagsQueue.Enqueue(Tag);
- }
-
- Callback();
- }
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
-
- protected virtual void Dispose(bool Disposing)
- {
- if (Disposing && !Disposed)
- {
- Disposed = true;
-
- AL.DeleteSource(SourceId);
-
- foreach (int Id in Buffers.Values)
- {
- AL.DeleteBuffer(Id);
- }
- }
- }
- }
-
- private ConcurrentDictionary Tracks;
-
- private Thread AudioPollerThread;
-
- private bool KeepPolling;
-
- public OpenALAudioOut()
- {
- Context = new AudioContext();
-
- Tracks = new ConcurrentDictionary();
-
- KeepPolling = true;
-
- AudioPollerThread = new Thread(AudioPollerWork);
-
- AudioPollerThread.Start();
- }
+ ///
+ /// The audio context
+ ///
+ private AudioContext _context;
///
- /// True if OpenAL is supported on the device.
+ /// An object pool containing objects
+ ///
+ private ConcurrentDictionary _tracks;
+
+ ///
+ /// True if the thread need to keep polling
+ ///
+ private bool _keepPolling;
+
+ ///
+ /// The poller thread audio context
+ ///
+ private Thread _audioPollerThread;
+
+ ///
+ /// The volume of audio renderer
+ ///
+ private float _volume = 1.0f;
+
+ ///
+ /// True if the volume of audio renderer have changed
+ ///
+ private bool _volumeChanged;
+
+ ///
+ /// True if OpenAL is supported on the device
///
public static bool IsSupported
{
@@ -197,157 +65,232 @@ namespace Ryujinx.Audio
}
}
+ public OpenALAudioOut()
+ {
+ _context = new AudioContext();
+ _tracks = new ConcurrentDictionary();
+ _keepPolling = true;
+ _audioPollerThread = new Thread(AudioPollerWork);
+
+ _audioPollerThread.Start();
+ }
+
private void AudioPollerWork()
{
do
{
- foreach (Track Td in Tracks.Values)
+ foreach (OpenALAudioTrack track in _tracks.Values)
{
- lock (Td)
+ lock (track)
{
- Td.CallReleaseCallbackIfNeeded();
+ track.CallReleaseCallbackIfNeeded();
}
}
// If it's not slept it will waste cycles.
Thread.Sleep(10);
}
- while (KeepPolling);
+ while (_keepPolling);
- foreach (Track Td in Tracks.Values)
+ foreach (OpenALAudioTrack track in _tracks.Values)
{
- Td.Dispose();
+ track.Dispose();
}
- Tracks.Clear();
+ _tracks.Clear();
}
- public int OpenTrack(int SampleRate, int Channels, ReleaseCallback Callback)
+ ///
+ /// Creates a new audio track with the specified parameters
+ ///
+ /// The requested sample rate
+ /// The requested channels
+ /// A that represents the delegate to invoke when a buffer has been released by the audio track
+ public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback)
{
- Track Td = new Track(SampleRate, GetALFormat(Channels), Callback);
+ OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(channels), callback);
- for (int Id = 0; Id < MaxTracks; Id++)
+ for (int id = 0; id < MaxTracks; id++)
{
- if (Tracks.TryAdd(Id, Td))
+ if (_tracks.TryAdd(id, track))
{
- return Id;
+ return id;
}
}
return -1;
}
- private ALFormat GetALFormat(int Channels)
+ private ALFormat GetALFormat(int channels)
{
- switch (Channels)
+ switch (channels)
{
case 1: return ALFormat.Mono16;
case 2: return ALFormat.Stereo16;
case 6: return ALFormat.Multi51Chn16Ext;
}
- throw new ArgumentOutOfRangeException(nameof(Channels));
+ throw new ArgumentOutOfRangeException(nameof(channels));
}
- public void CloseTrack(int Track)
+ ///
+ /// Stops playback and closes the track specified by
+ ///
+ /// The ID of the track to close
+ public void CloseTrack(int trackId)
{
- if (Tracks.TryRemove(Track, out Track Td))
+ if (_tracks.TryRemove(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- Td.Dispose();
+ track.Dispose();
}
}
}
- public bool ContainsBuffer(int Track, long Tag)
+ ///
+ /// Returns a value indicating whether the specified buffer is currently reserved by the specified track
+ ///
+ /// The track to check
+ /// The buffer tag to check
+ public bool ContainsBuffer(int trackId, long bufferTag)
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- return Td.ContainsBuffer(Tag);
+ return track.ContainsBuffer(bufferTag);
}
}
return false;
}
- public long[] GetReleasedBuffers(int Track, int MaxCount)
+ ///
+ /// Gets a list of buffer tags the specified track is no longer reserving
+ ///
+ /// The track to retrieve buffer tags from
+ /// The maximum amount of buffer tags to retrieve
+ /// Buffers released by the specified track
+ public long[] GetReleasedBuffers(int trackId, int maxCount)
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- return Td.GetReleasedBuffers(MaxCount);
+ return track.GetReleasedBuffers(maxCount);
}
}
return null;
}
- public void AppendBuffer(int Track, long Tag, T[] Buffer) where T : struct
+ ///
+ /// Appends an audio buffer to the specified track
+ ///
+ /// The sample type of the buffer
+ /// The track to append the buffer to
+ /// The internal tag of the buffer
+ /// The buffer to append to the track
+ public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- int BufferId = Td.AppendBuffer(Tag);
+ int bufferId = track.AppendBuffer(bufferTag);
- int Size = Buffer.Length * Marshal.SizeOf();
+ int size = buffer.Length * Marshal.SizeOf();
- AL.BufferData(BufferId, Td.Format, Buffer, Size, Td.SampleRate);
+ AL.BufferData(bufferId, track.Format, buffer, size, track.SampleRate);
- AL.SourceQueueBuffer(Td.SourceId, BufferId);
+ AL.SourceQueueBuffer(track.SourceId, bufferId);
- StartPlaybackIfNeeded(Td);
+ StartPlaybackIfNeeded(track);
}
}
}
- public void Start(int Track)
+ ///
+ /// Starts playback
+ ///
+ /// The ID of the track to start playback on
+ public void Start(int trackId)
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- Td.State = PlaybackState.Playing;
+ track.State = PlaybackState.Playing;
- StartPlaybackIfNeeded(Td);
+ StartPlaybackIfNeeded(track);
}
}
}
- private void StartPlaybackIfNeeded(Track Td)
+ private void StartPlaybackIfNeeded(OpenALAudioTrack track)
{
- AL.GetSource(Td.SourceId, ALGetSourcei.SourceState, out int StateInt);
+ AL.GetSource(track.SourceId, ALGetSourcei.SourceState, out int stateInt);
- ALSourceState State = (ALSourceState)StateInt;
+ ALSourceState State = (ALSourceState)stateInt;
- if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing)
+ if (State != ALSourceState.Playing && track.State == PlaybackState.Playing)
{
- AL.SourcePlay(Td.SourceId);
+ if (_volumeChanged)
+ {
+ AL.Source(track.SourceId, ALSourcef.Gain, _volume);
+
+ _volumeChanged = false;
+ }
+
+ AL.SourcePlay(track.SourceId);
}
}
- public void Stop(int Track)
+ ///
+ /// Stops playback
+ ///
+ /// The ID of the track to stop playback on
+ public void Stop(int trackId)
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
{
- lock (Td)
+ lock (track)
{
- Td.State = PlaybackState.Stopped;
+ track.State = PlaybackState.Stopped;
- AL.SourceStop(Td.SourceId);
+ AL.SourceStop(track.SourceId);
}
}
}
- public PlaybackState GetState(int Track)
+ ///
+ /// Get playback volume
+ ///
+ public float GetVolume() => _volume;
+
+ ///
+ /// Set playback volume
+ ///
+ /// The volume of the playback
+ public void SetVolume(float volume)
{
- if (Tracks.TryGetValue(Track, out Track Td))
+ if (!_volumeChanged)
{
- return Td.State;
+ _volume = volume;
+ _volumeChanged = true;
+ }
+ }
+
+ ///
+ /// Gets the current playback state of the specified track
+ ///
+ /// The track to retrieve the playback state for
+ public PlaybackState GetState(int trackId)
+ {
+ if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track))
+ {
+ return track.State;
}
return PlaybackState.Stopped;
@@ -358,11 +301,11 @@ namespace Ryujinx.Audio
Dispose(true);
}
- protected virtual void Dispose(bool Disposing)
+ protected virtual void Dispose(bool disposing)
{
- if (Disposing)
+ if (disposing)
{
- KeepPolling = false;
+ _keepPolling = false;
}
}
}
diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs
new file mode 100644
index 0000000000..8629dc969c
--- /dev/null
+++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs
@@ -0,0 +1,142 @@
+using OpenTK.Audio.OpenAL;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.Audio
+{
+ internal class OpenALAudioTrack : IDisposable
+ {
+ public int SourceId { get; private set; }
+ public int SampleRate { get; private set; }
+ public ALFormat Format { get; private set; }
+ public PlaybackState State { get; set; }
+
+ private ReleaseCallback _callback;
+
+ private ConcurrentDictionary _buffers;
+
+ private Queue _queuedTagsQueue;
+ private Queue _releasedTagsQueue;
+
+ private bool _disposed;
+
+ public OpenALAudioTrack(int sampleRate, ALFormat format, ReleaseCallback callback)
+ {
+ SampleRate = sampleRate;
+ Format = format;
+ State = PlaybackState.Stopped;
+ SourceId = AL.GenSource();
+
+ _callback = callback;
+
+ _buffers = new ConcurrentDictionary();
+
+ _queuedTagsQueue = new Queue();
+ _releasedTagsQueue = new Queue();
+ }
+
+ public bool ContainsBuffer(long tag)
+ {
+ foreach (long queuedTag in _queuedTagsQueue)
+ {
+ if (queuedTag == tag)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public long[] GetReleasedBuffers(int count)
+ {
+ AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount);
+
+ releasedCount += _releasedTagsQueue.Count;
+
+ if (count > releasedCount)
+ {
+ count = releasedCount;
+ }
+
+ List tags = new List();
+
+ while (count-- > 0 && _releasedTagsQueue.TryDequeue(out long tag))
+ {
+ tags.Add(tag);
+ }
+
+ while (count-- > 0 && _queuedTagsQueue.TryDequeue(out long tag))
+ {
+ AL.SourceUnqueueBuffers(SourceId, 1);
+
+ tags.Add(tag);
+ }
+
+ return tags.ToArray();
+ }
+
+ public int AppendBuffer(long tag)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ int id = AL.GenBuffer();
+
+ _buffers.AddOrUpdate(tag, id, (key, oldId) =>
+ {
+ AL.DeleteBuffer(oldId);
+
+ return id;
+ });
+
+ _queuedTagsQueue.Enqueue(tag);
+
+ return id;
+ }
+
+ public void CallReleaseCallbackIfNeeded()
+ {
+ AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount);
+
+ if (releasedCount > 0)
+ {
+ // If we signal, then we also need to have released buffers available
+ // to return when GetReleasedBuffers is called.
+ // If playback needs to be re-started due to all buffers being processed,
+ // then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue.
+ while (releasedCount-- > 0 && _queuedTagsQueue.TryDequeue(out long tag))
+ {
+ AL.SourceUnqueueBuffers(SourceId, 1);
+
+ _releasedTagsQueue.Enqueue(tag);
+ }
+
+ _callback();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ AL.DeleteSource(SourceId);
+
+ foreach (int id in _buffers.Values)
+ {
+ AL.DeleteBuffer(id);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs
index f5e4c57d9e..1e487a6d93 100644
--- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs
+++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs
@@ -1,5 +1,6 @@
using Ryujinx.Audio.SoundIo;
using SoundIOSharp;
+using System;
using System.Collections.Generic;
namespace Ryujinx.Audio
@@ -14,23 +15,33 @@ namespace Ryujinx.Audio
///
private const int MaximumTracks = 256;
+ ///
+ /// The volume of audio renderer
+ ///
+ private float _volume = 1.0f;
+
+ ///
+ /// True if the volume of audio renderer have changed
+ ///
+ private bool _volumeChanged;
+
///
/// The audio context
///
- private SoundIO m_AudioContext;
+ private SoundIO _audioContext;
///
/// The audio device
///
- private SoundIODevice m_AudioDevice;
+ private SoundIODevice _audioDevice;
///
/// An object pool containing objects
///
- private SoundIoAudioTrackPool m_TrackPool;
+ private SoundIoAudioTrackPool _trackPool;
///
- /// True if SoundIO is supported on the device.
+ /// True if SoundIO is supported on the device
///
public static bool IsSupported
{
@@ -45,27 +56,13 @@ namespace Ryujinx.Audio
///
public SoundIoAudioOut()
{
- m_AudioContext = new SoundIO();
+ _audioContext = new SoundIO();
- m_AudioContext.Connect();
- m_AudioContext.FlushEvents();
+ _audioContext.Connect();
+ _audioContext.FlushEvents();
- m_AudioDevice = FindNonRawDefaultAudioDevice(m_AudioContext, true);
- m_TrackPool = new SoundIoAudioTrackPool(m_AudioContext, m_AudioDevice, MaximumTracks);
- }
-
- ///
- /// Gets the current playback state of the specified track
- ///
- /// The track to retrieve the playback state for
- public PlaybackState GetState(int trackId)
- {
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
- {
- return track.State;
- }
-
- return PlaybackState.Stopped;
+ _audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true);
+ _trackPool = new SoundIoAudioTrackPool(_audioContext, _audioDevice, MaximumTracks);
}
///
@@ -77,7 +74,7 @@ namespace Ryujinx.Audio
/// The created track's Track ID
public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback)
{
- if (!m_TrackPool.TryGet(out SoundIoAudioTrack track))
+ if (!_trackPool.TryGet(out SoundIoAudioTrack track))
{
return -1;
}
@@ -94,53 +91,13 @@ namespace Ryujinx.Audio
/// The ID of the track to close
public void CloseTrack(int trackId)
{
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
{
// Close and dispose of the track
track.Close();
// Recycle the track back into the pool
- m_TrackPool.Put(track);
- }
- }
-
- ///
- /// Starts playback
- ///
- /// The ID of the track to start playback on
- public void Start(int trackId)
- {
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
- {
- track.Start();
- }
- }
-
- ///
- /// Stops playback
- ///
- /// The ID of the track to stop playback on
- public void Stop(int trackId)
- {
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
- {
- track.Stop();
- }
- }
-
- ///
- /// Appends an audio buffer to the specified track
- ///
- /// The sample type of the buffer
- /// The track to append the buffer to
- /// The internal tag of the buffer
- /// The buffer to append to the track
- public void AppendBuffer(int trackId, long bufferTag, T[] buffer)
- where T : struct
- {
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
- {
- track.AppendBuffer(bufferTag, buffer);
+ _trackPool.Put(track);
}
}
@@ -151,7 +108,7 @@ namespace Ryujinx.Audio
/// The buffer tag to check
public bool ContainsBuffer(int trackId, long bufferTag)
{
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
{
return track.ContainsBuffer(bufferTag);
}
@@ -167,7 +124,7 @@ namespace Ryujinx.Audio
/// Buffers released by the specified track
public long[] GetReleasedBuffers(int trackId, int maxCount)
{
- if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
{
List bufferTags = new List();
@@ -182,14 +139,92 @@ namespace Ryujinx.Audio
return new long[0];
}
+ ///
+ /// Appends an audio buffer to the specified track
+ ///
+ /// The sample type of the buffer
+ /// The track to append the buffer to
+ /// The internal tag of the buffer
+ /// The buffer to append to the track
+ public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct
+ {
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ {
+ if (_volumeChanged)
+ {
+ track.AudioStream.SetVolume(_volume);
+
+ _volumeChanged = false;
+ }
+
+ track.AppendBuffer(bufferTag, buffer);
+ }
+ }
+
+ ///
+ /// Starts playback
+ ///
+ /// The ID of the track to start playback on
+ public void Start(int trackId)
+ {
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ {
+ track.Start();
+ }
+ }
+
+ ///
+ /// Stops playback
+ ///
+ /// The ID of the track to stop playback on
+ public void Stop(int trackId)
+ {
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ {
+ track.Stop();
+ }
+ }
+
+ ///
+ /// Get playback volume
+ ///
+ public float GetVolume() => _volume;
+
+ ///
+ /// Set playback volume
+ ///
+ /// The volume of the playback
+ public void SetVolume(float volume)
+ {
+ if (!_volumeChanged)
+ {
+ _volume = volume;
+ _volumeChanged = true;
+ }
+ }
+
+ ///
+ /// Gets the current playback state of the specified track
+ ///
+ /// The track to retrieve the playback state for
+ public PlaybackState GetState(int trackId)
+ {
+ if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track))
+ {
+ return track.State;
+ }
+
+ return PlaybackState.Stopped;
+ }
+
///
/// Releases the unmanaged resources used by the
///
public void Dispose()
{
- m_TrackPool.Dispose();
- m_AudioContext.Disconnect();
- m_AudioContext.Dispose();
+ _trackPool.Dispose();
+ _audioContext.Disconnect();
+ _audioContext.Dispose();
}
///
diff --git a/Ryujinx.HLE/Utilities/EndianSwap.cs b/Ryujinx.Common/Utilities/EndianSwap.cs
similarity index 55%
rename from Ryujinx.HLE/Utilities/EndianSwap.cs
rename to Ryujinx.Common/Utilities/EndianSwap.cs
index df08191ac6..049570e32a 100644
--- a/Ryujinx.HLE/Utilities/EndianSwap.cs
+++ b/Ryujinx.Common/Utilities/EndianSwap.cs
@@ -1,6 +1,8 @@
-namespace Ryujinx.HLE.Utilities
+using System;
+
+namespace Ryujinx.Common
{
- static class EndianSwap
+ public static class EndianSwap
{
public static ushort Swap16(ushort value) => (ushort)(((value >> 8) & 0xff) | (value << 8));
@@ -13,5 +15,17 @@
((uintVal << 8) & 0x00ff0000) |
((uintVal << 24) & 0xff000000));
}
+
+ public static uint FromBigEndianToPlatformEndian(uint value)
+ {
+ uint result = value;
+
+ if (BitConverter.IsLittleEndian)
+ {
+ result = (uint)EndianSwap.Swap32((int)result);
+ }
+
+ return result;
+ }
}
}
diff --git a/Ryujinx.HLE/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs
index 0ead17473b..38864bc2d7 100644
--- a/Ryujinx.HLE/DeviceMemory.cs
+++ b/Ryujinx.HLE/DeviceMemory.cs
@@ -59,6 +59,11 @@ namespace Ryujinx.HLE
return *((ulong*)(_ramPtr + position));
}
+ public unsafe T ReadStruct(long position)
+ {
+ return Marshal.PtrToStructure((IntPtr)(_ramPtr + position));
+ }
+
public void WriteSByte(long position, sbyte value)
{
WriteByte(position, (byte)value);
diff --git a/Ryujinx.HLE/Exceptions/InternalServiceException.cs b/Ryujinx.HLE/Exceptions/InternalServiceException.cs
new file mode 100644
index 0000000000..b940c51c89
--- /dev/null
+++ b/Ryujinx.HLE/Exceptions/InternalServiceException.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.HLE.Exceptions
+{
+ class InternalServiceException: Exception
+ {
+ public InternalServiceException(string message) : base(message) { }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
index fe6642c3e3..9ed2e1422b 100644
--- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
+++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
@@ -1,6 +1,6 @@
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
-using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
@@ -143,7 +143,7 @@ namespace Ryujinx.HLE.FileSystem.Content
}
}
- TimeZoneManager.Instance.Initialize(_device);
+ TimeManager.Instance.InitializeTimeZone(_device);
}
public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)
diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
index dfb87f3c94..8a936dbf55 100644
--- a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
+++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
@@ -1,9 +1,9 @@
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
+using Ryujinx.Common;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.Resource;
-using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.IO;
using static Ryujinx.HLE.Utilities.FontUtils;
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index c38e0e2464..46b04fe4c5 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -9,12 +9,14 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.HLE.HOS.Services.Sm;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -23,7 +25,8 @@ using System.Linq;
using System.Reflection;
using System.Threading;
-using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
+using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager;
+using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
namespace Ryujinx.HLE.HOS
{
@@ -88,8 +91,6 @@ namespace Ryujinx.HLE.HOS
internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; }
internal KSharedMemory IirsSharedMem { get; private set; }
- internal KSharedMemory TimeSharedMem { get; private set; }
-
internal SharedFontManager Font { get; private set; }
internal ContentManager ContentManager { get; private set; }
@@ -185,7 +186,10 @@ namespace Ryujinx.HLE.HOS
HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read);
FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read);
IirsSharedMem = new KSharedMemory(this, iirsPageList, 0, 0, MemoryPermission.Read);
- TimeSharedMem = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
+
+ KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
+
+ TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize);
AppletState = new AppletStateMgr(this);
@@ -201,17 +205,30 @@ namespace Ryujinx.HLE.HOS
ContentManager = new ContentManager(device);
- // TODO: use set:sys (and set external clock source id from settings)
+ // TODO: use set:sys (and get external clock source id from settings)
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
- StandardSteadyClockCore.Instance.ConfigureSetupValue();
+ UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+ // We assume the rtc is system time.
+ TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue);
+
+ // First init the standard steady clock
+ TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, TimeSpanType.Zero, TimeSpanType.Zero, false);
+ TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds());
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
{
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
- StandardNetworkSystemClockCore.Instance.SetStandardNetworkClockSufficientAccuracy(standardNetworkClockSufficientAccuracy);
+ TimeServiceManager.Instance.SetupStandardNetworkSystemClock(new SystemClockContext(), standardNetworkClockSufficientAccuracy);
}
+ TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom());
+
+ // FIXME: TimeZone shoud be init here but it's actually done in ContentManager
+
+ TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
}
public void LoadCart(string exeFsDir, string romFsFile = null)
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
index 50ab3d1000..beb7878fdd 100644
--- a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
+++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
@@ -96,6 +96,7 @@ namespace Ryujinx.HLE.HOS.Ipc
else if (request.Type == IpcMessageType.CloseSession)
{
// TODO
+ return KernelResult.PortRemoteClosed;
}
else
{
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs
index 5b6983d651..11d8036cd1 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs
@@ -147,6 +147,32 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager
return ResultCode.Success;
}
+ [Command(12)] // 6.0.0+
+ // SetAudioOutVolume(s32)
+ public ResultCode SetAudioOutVolume(ServiceCtx context)
+ {
+ // Games send a gain value here, so we need to apply it on the current volume value.
+
+ float gain = context.RequestData.ReadSingle();
+ float currentVolume = _audioOut.GetVolume();
+ float newVolume = Math.Clamp(currentVolume + gain, 0.0f, 1.0f);
+
+ _audioOut.SetVolume(newVolume);
+
+ return ResultCode.Success;
+ }
+
+ [Command(13)] // 6.0.0+
+ // GetAudioOutVolume() -> s32
+ public ResultCode GetAudioOutVolume(ServiceCtx context)
+ {
+ float volume = _audioOut.GetVolume();
+
+ context.ResponseData.Write(volume);
+
+ return ResultCode.Success;
+ }
+
public void Dispose()
{
Dispose(true);
diff --git a/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
index e23398dfe8..079f2ae75e 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
@@ -1,13 +1,18 @@
+using Concentus;
+using Concentus.Enums;
using Concentus.Structs;
+using Ryujinx.HLE.HOS.Services.Audio.Types;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class IHardwareOpusDecoder : IpcService
{
- private const int FixedSampleRate = 48000;
-
- private int _sampleRate;
- private int _channelsCount;
+ private int _sampleRate;
+ private int _channelsCount;
+ private bool _reset;
private OpusDecoder _decoder;
@@ -15,65 +20,211 @@ namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
_sampleRate = sampleRate;
_channelsCount = channelsCount;
+ _reset = false;
- _decoder = new OpusDecoder(FixedSampleRate, channelsCount);
+ _decoder = new OpusDecoder(sampleRate, channelsCount);
}
- [Command(0)]
- // DecodeInterleaved(buffer) -> (u32, u32, buffer)
- public ResultCode DecodeInterleaved(ServiceCtx context)
+ private ResultCode GetPacketNumSamples(out int numSamples, byte[] packet)
{
- long inPosition = context.Request.SendBuff[0].Position;
- long inSize = context.Request.SendBuff[0].Size;
+ int result = OpusPacketInfo.GetNumSamples(_decoder, packet, 0, packet.Length);
- if (inSize < 8)
+ numSamples = result;
+
+ if (result == OpusError.OPUS_INVALID_PACKET)
{
return ResultCode.OpusInvalidInput;
}
-
- long outPosition = context.Request.ReceiveBuff[0].Position;
- long outSize = context.Request.ReceiveBuff[0].Size;
-
- byte[] opusData = context.Memory.ReadBytes(inPosition, inSize);
-
- int processed = ((opusData[0] << 24) |
- (opusData[1] << 16) |
- (opusData[2] << 8) |
- (opusData[3] << 0)) + 8;
-
- if ((uint)processed > (ulong)inSize)
+ else if (result == OpusError.OPUS_BAD_ARG)
{
return ResultCode.OpusInvalidInput;
}
- short[] pcm = new short[outSize / 2];
-
- int frameSize = pcm.Length / (_channelsCount * 2);
-
- int samples = _decoder.Decode(opusData, 0, opusData.Length, pcm, 0, frameSize);
-
- foreach (short sample in pcm)
- {
- context.Memory.WriteInt16(outPosition, sample);
-
- outPosition += 2;
- }
-
- context.ResponseData.Write(processed);
- context.ResponseData.Write(samples);
-
return ResultCode.Success;
}
- [Command(4)]
- // DecodeInterleavedWithPerf(buffer) -> (u32, u32, u64, buffer)
- public ResultCode DecodeInterleavedWithPerf(ServiceCtx context)
+ private ResultCode DecodeInterleavedInternal(BinaryReader input, out short[] outPcmData, long outputSize, out uint outConsumed, out int outSamples)
{
- ResultCode result = DecodeInterleaved(context);
+ outPcmData = null;
+ outConsumed = 0;
+ outSamples = 0;
- // TODO: Figure out what this value is.
- // According to switchbrew, it is now used.
- context.ResponseData.Write(0L);
+ long streamSize = input.BaseStream.Length;
+
+ if (streamSize < Marshal.SizeOf())
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ OpusPacketHeader header = OpusPacketHeader.FromStream(input);
+
+ uint totalSize = header.length + (uint)Marshal.SizeOf();
+
+ if (totalSize > streamSize)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ byte[] opusData = input.ReadBytes((int)header.length);
+
+ ResultCode result = GetPacketNumSamples(out int numSamples, opusData);
+
+ if (result == ResultCode.Success)
+ {
+ if ((uint)numSamples * (uint)_channelsCount * sizeof(short) > outputSize)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ outPcmData = new short[numSamples * _channelsCount];
+
+ if (_reset)
+ {
+ _reset = false;
+
+ _decoder.ResetState();
+ }
+
+ try
+ {
+ outSamples = _decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / _channelsCount);
+ outConsumed = totalSize;
+ }
+ catch (OpusException)
+ {
+ // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
+ return ResultCode.OpusInvalidInput;
+ }
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(0)]
+ // DecodeInterleaved(buffer) -> (u32, u32, buffer)
+ public ResultCode DecodeInterleavedOriginal(ServiceCtx context)
+ {
+ ResultCode result;
+
+ long inPosition = context.Request.SendBuff[0].Position;
+ long inSize = context.Request.SendBuff[0].Size;
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize))))
+ {
+ result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
+
+ if (result == ResultCode.Success)
+ {
+ byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
+ Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
+ context.Memory.WriteBytes(outputPosition, pcmDataBytes);
+
+ context.ResponseData.Write(outConsumed);
+ context.ResponseData.Write(outSamples);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(4)] // 6.0.0+
+ // DecodeInterleavedWithPerfOld(buffer) -> (u32, u32, u64, buffer)
+ public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
+ {
+ ResultCode result;
+
+ long inPosition = context.Request.SendBuff[0].Position;
+ long inSize = context.Request.SendBuff[0].Size;
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize))))
+ {
+ result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
+
+ if (result == ResultCode.Success)
+ {
+ byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
+ Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
+ context.Memory.WriteBytes(outputPosition, pcmDataBytes);
+
+ context.ResponseData.Write(outConsumed);
+ context.ResponseData.Write(outSamples);
+
+ // This is the time the DSP took to process the request, TODO: fill this.
+ context.ResponseData.Write(0);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(6)] // 6.0.0+
+ // DecodeInterleavedOld(bool reset, buffer) -> (u32, u32, u64, buffer)
+ public ResultCode DecodeInterleavedOld(ServiceCtx context)
+ {
+ ResultCode result;
+
+ _reset = context.RequestData.ReadBoolean();
+
+ long inPosition = context.Request.SendBuff[0].Position;
+ long inSize = context.Request.SendBuff[0].Size;
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize))))
+ {
+ result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
+
+ if (result == ResultCode.Success)
+ {
+ byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
+ Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
+ context.Memory.WriteBytes(outputPosition, pcmDataBytes);
+
+ context.ResponseData.Write(outConsumed);
+ context.ResponseData.Write(outSamples);
+
+ // This is the time the DSP took to process the request, TODO: fill this.
+ context.ResponseData.Write(0);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(8)] // 7.0.0+
+ // DecodeInterleaved(bool reset, buffer) -> (u32, u32, u64, buffer)
+ public ResultCode DecodeInterleaved(ServiceCtx context)
+ {
+ ResultCode result;
+
+ _reset = context.RequestData.ReadBoolean();
+
+ long inPosition = context.Request.SendBuff[0].Position;
+ long inSize = context.Request.SendBuff[0].Size;
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize))))
+ {
+ result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
+
+ if (result == ResultCode.Success)
+ {
+ byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
+ Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
+ context.Memory.WriteBytes(outputPosition, pcmDataBytes);
+
+ context.ResponseData.Write(outConsumed);
+ context.ResponseData.Write(outSamples);
+
+ // This is the time the DSP took to process the request, TODO: fill this.
+ context.ResponseData.Write(0);
+ }
+ }
return result;
}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
new file mode 100644
index 0000000000..bb4b6d16c9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Common;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct OpusPacketHeader
+ {
+ public uint length;
+ public uint finalRange;
+
+ public static OpusPacketHeader FromStream(BinaryReader reader)
+ {
+ OpusPacketHeader header = reader.ReadStruct();
+
+ header.length = EndianSwap.FromBigEndianToPlatformEndian(header.length);
+ header.finalRange = EndianSwap.FromBigEndianToPlatformEndian(header.finalRange);
+
+ return header;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
index b679005e6b..ca3853e8b8 100644
--- a/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
+++ b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
@@ -1704,6 +1704,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
{ "time!standard_steady_clock_test_offset_minutes", 0 },
{ "time!standard_steady_clock_rtc_update_interval_minutes", 5 },
{ "time!standard_network_clock_sufficient_accuracy_minutes", 43200 },
+ { "time!standard_user_clock_initial_year", 2019 },
{ "usb!usb30_force_enabled", false },
{ "wlan_debug!skip_wlan_boot", false }
};
diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
index 3a02e06c05..7db8066a6b 100644
--- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
+++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
@@ -1,4 +1,5 @@
-using Ryujinx.Common.Logging;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.Net;
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs
new file mode 100644
index 0000000000..14d3cb244a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ protected override ResultCode Update()
+ {
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
index 8e9073a40a..003863e4cf 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
@@ -2,26 +2,6 @@
{
class EphemeralNetworkSystemClockCore : SystemClockCore
{
- private static EphemeralNetworkSystemClockCore _instance;
-
- public static EphemeralNetworkSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new EphemeralNetworkSystemClockCore(TickBasedSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { }
-
- public override ResultCode Flush(SystemClockContext context)
- {
- return ResultCode.Success;
- }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs
new file mode 100644
index 0000000000..fb7ebdc5b6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class LocalSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ private TimeSharedMemory _sharedMemory;
+
+ public LocalSystemClockContextWriter(TimeSharedMemory sharedMemory)
+ {
+ _sharedMemory = sharedMemory;
+ }
+
+ protected override ResultCode Update()
+ {
+ _sharedMemory.UpdateLocalSystemClockContext(_context);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs
new file mode 100644
index 0000000000..36468ec141
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class NetworkSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ private TimeSharedMemory _sharedMemory;
+
+ public NetworkSystemClockContextWriter(TimeSharedMemory sharedMemory)
+ {
+ _sharedMemory = sharedMemory;
+ }
+
+ protected override ResultCode Update()
+ {
+ _sharedMemory.UpdateNetworkSystemClockContext(_context);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
index a172797608..20c334e862 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
@@ -2,28 +2,6 @@
{
class StandardLocalSystemClockCore : SystemClockCore
{
- private static StandardLocalSystemClockCore _instance;
-
- public static StandardLocalSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardLocalSystemClockCore(StandardSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {}
-
- public override ResultCode Flush(SystemClockContext context)
- {
- // TODO: set:sys SetUserSystemClockContext
-
- return ResultCode.Success;
- }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
index cc21dd9a5f..b86f703dbe 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
@@ -6,33 +6,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
private TimeSpanType _standardNetworkClockSufficientAccuracy;
- private static StandardNetworkSystemClockCore _instance;
-
- public static StandardNetworkSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardNetworkSystemClockCore(StandardSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore)
{
_standardNetworkClockSufficientAccuracy = new TimeSpanType(0);
}
- public override ResultCode Flush(SystemClockContext context)
- {
- // TODO: set:sys SetNetworkSystemClockContext
-
- return ResultCode.Success;
- }
-
public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread)
{
SteadyClockCore steadyClockCore = GetSteadyClockCore();
@@ -40,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
bool isStandardNetworkClockSufficientAccuracy = false;
- ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
+ ResultCode result = GetClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success)
{
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
index 1bc5bee784..370e7d73c9 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
@@ -1,49 +1,30 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class StandardSteadyClockCore : SteadyClockCore
{
- private long _setupValue;
- private ResultCode _setupResultCode;
- private bool _isRtcResetDetected;
+ private TimeSpanType _setupValue;
private TimeSpanType _testOffset;
private TimeSpanType _internalOffset;
+ private TimeSpanType _cachedRawTimePoint;
- private static StandardSteadyClockCore _instance;
-
- public static StandardSteadyClockCore Instance
+ public StandardSteadyClockCore()
{
- get
- {
- if (_instance == null)
- {
- _instance = new StandardSteadyClockCore();
- }
-
- return _instance;
- }
- }
-
- private StandardSteadyClockCore()
- {
- _testOffset = new TimeSpanType(0);
- _internalOffset = new TimeSpanType(0);
+ _setupValue = TimeSpanType.Zero;
+ _testOffset = TimeSpanType.Zero;
+ _internalOffset = TimeSpanType.Zero;
+ _cachedRawTimePoint = TimeSpanType.Zero;
}
public override SteadyClockTimePoint GetTimePoint(KThread thread)
{
SteadyClockTimePoint result = new SteadyClockTimePoint
{
- TimePoint = 0,
+ TimePoint = GetCurrentRawTimePoint(thread).ToSeconds(),
ClockSourceId = GetClockSourceId()
};
- TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
-
- result.TimePoint = _setupValue + ticksTimeSpan.ToSeconds();
-
return result;
}
@@ -57,16 +38,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_testOffset = testOffset;
}
- public override ResultCode GetRtcValue(out ulong rtcValue)
- {
- return (ResultCode)IRtcManager.GetExternalRtcValue(out rtcValue);
- }
-
- public bool IsRtcResetDetected()
- {
- return _isRtcResetDetected;
- }
-
public override TimeSpanType GetInternalOffset()
{
return _internalOffset;
@@ -77,31 +48,35 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_internalOffset = internalOffset;
}
- public override ResultCode GetSetupResultValue()
+ public override TimeSpanType GetCurrentRawTimePoint(KThread thread)
{
- return _setupResultCode;
- }
+ TimeSpanType ticksTimeSpan;
- public void ConfigureSetupValue()
- {
- int retry = 0;
-
- ResultCode result = ResultCode.Success;
-
- while (retry < 20)
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
{
- result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
-
- if (result == ResultCode.Success)
- {
- _setupValue = (long)rtcValue;
- break;
- }
-
- retry++;
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
}
- _setupResultCode = result;
+ TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds);
+
+ if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds)
+ {
+ rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds;
+ }
+
+ _cachedRawTimePoint = rawTimePoint;
+
+ return rawTimePoint;
+ }
+
+ public void SetSetupValue(TimeSpanType setupValue)
+ {
+ _setupValue = setupValue;
}
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
index c98b0064b6..42bc05fabe 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
@@ -7,35 +8,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
private StandardLocalSystemClockCore _localSystemClockCore;
private StandardNetworkSystemClockCore _networkSystemClockCore;
private bool _autoCorrectionEnabled;
-
- private static StandardUserSystemClockCore _instance;
-
- public static StandardUserSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardUserSystemClockCore(StandardLocalSystemClockCore.Instance, StandardNetworkSystemClockCore.Instance);
- }
-
- return _instance;
- }
- }
+ private SteadyClockTimePoint _autoCorrectionTime;
+ private KEvent _autoCorrectionEvent;
public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore())
{
_localSystemClockCore = localSystemClockCore;
_networkSystemClockCore = networkSystemClockCore;
_autoCorrectionEnabled = false;
+ _autoCorrectionTime = SteadyClockTimePoint.GetRandom();
+ _autoCorrectionEvent = null;
}
- public override ResultCode Flush(SystemClockContext context)
+ protected override ResultCode Flush(SystemClockContext context)
{
- return ResultCode.NotImplemented;
+ // As UserSystemClock isn't a real system clock, this shouldn't happens.
+ throw new NotImplementedException();
}
- public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+ public override ResultCode GetClockContext(KThread thread, out SystemClockContext context)
{
ResultCode result = ApplyAutomaticCorrection(thread, false);
@@ -43,13 +34,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (result == ResultCode.Success)
{
- return _localSystemClockCore.GetSystemClockContext(thread, out context);
+ return _localSystemClockCore.GetClockContext(thread, out context);
}
return result;
}
- public override ResultCode SetSystemClockContext(SystemClockContext context)
+ public override ResultCode SetClockContext(SystemClockContext context)
{
return ResultCode.NotImplemented;
}
@@ -60,17 +51,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread))
{
- result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context);
+ result = _networkSystemClockCore.GetClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success)
{
- _localSystemClockCore.SetSystemClockContext(context);
+ _localSystemClockCore.SetClockContext(context);
}
}
return result;
}
+ internal void CreateAutomaticCorrectionEvent(Horizon system)
+ {
+ _autoCorrectionEvent = new KEvent(system);
+ }
+
public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled)
{
ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled);
@@ -87,5 +83,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
return _autoCorrectionEnabled;
}
+
+ public KReadableEvent GetAutomaticCorrectionReadableEvent()
+ {
+ return _autoCorrectionEvent.ReadableEvent;
+ }
+
+ public void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steadyClockTimePoint)
+ {
+ _autoCorrectionTime = steadyClockTimePoint;
+ }
+
+ public SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime()
+ {
+ return _autoCorrectionTime;
+ }
+
+ public void SignalAutomaticCorrectionEvent()
+ {
+ _autoCorrectionEvent.WritableEvent.Signal();
+ }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
index 54d9accf4f..83ace9814f 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
@@ -7,10 +7,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
abstract class SteadyClockCore
{
private UInt128 _clockSourceId;
+ private bool _isRtcResetDetected;
+ private bool _isInitialized;
public SteadyClockCore()
{
- _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ _isRtcResetDetected = false;
+ _isInitialized = false;
}
public UInt128 GetClockSourceId()
@@ -18,6 +22,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _clockSourceId;
}
+ public void SetClockSourceId(UInt128 clockSourceId)
+ {
+ _clockSourceId = clockSourceId;
+ }
+
+ public void SetRtcReset()
+ {
+ _isRtcResetDetected = true;
+ }
+
public virtual TimeSpanType GetTestOffset()
{
return new TimeSpanType(0);
@@ -25,16 +39,21 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public virtual void SetTestOffset(TimeSpanType testOffset) {}
- public virtual ResultCode GetRtcValue(out ulong rtcValue)
+ public ResultCode GetRtcValue(out ulong rtcValue)
{
rtcValue = 0;
return ResultCode.NotImplemented;
}
- public virtual ResultCode GetSetupResultValue()
+ public bool IsRtcResetDetected()
{
- return ResultCode.NotImplemented;
+ return _isRtcResetDetected;
+ }
+
+ public ResultCode GetSetupResultValue()
+ {
+ return ResultCode.Success;
}
public virtual TimeSpanType GetInternalOffset()
@@ -49,6 +68,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
throw new NotImplementedException();
}
+ public virtual TimeSpanType GetCurrentRawTimePoint(KThread thread)
+ {
+ SteadyClockTimePoint timePoint = GetTimePoint(thread);
+
+ return TimeSpanType.FromSeconds(timePoint.TimePoint);
+ }
+
public SteadyClockTimePoint GetCurrentTimePoint(KThread thread)
{
SteadyClockTimePoint result = GetTimePoint(thread);
@@ -58,5 +84,15 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return result;
}
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
new file mode 100644
index 0000000000..629d8ee114
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ abstract class SystemClockContextUpdateCallback
+ {
+ private List _operationEventList;
+ protected SystemClockContext _context;
+ private bool _hasContext;
+
+ public SystemClockContextUpdateCallback()
+ {
+ _operationEventList = new List();
+ _context = new SystemClockContext();
+ _hasContext = false;
+ }
+
+ private bool NeedUpdate(SystemClockContext context)
+ {
+ if (_hasContext)
+ {
+ return _context.Offset != context.Offset || _context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId;
+ }
+
+ return true;
+ }
+
+ public void RegisterOperationEvent(KWritableEvent writableEvent)
+ {
+ Monitor.Enter(_operationEventList);
+ _operationEventList.Add(writableEvent);
+ Monitor.Exit(_operationEventList);
+ }
+
+ private void BroadcastOperationEvent()
+ {
+ Monitor.Enter(_operationEventList);
+
+ foreach (KWritableEvent e in _operationEventList)
+ {
+ e.Signal();
+ }
+
+ Monitor.Exit(_operationEventList);
+ }
+
+ protected abstract ResultCode Update();
+
+ public ResultCode Update(SystemClockContext context)
+ {
+ ResultCode result = ResultCode.Success;
+
+ if (NeedUpdate(context))
+ {
+ _context = context;
+ _hasContext = true;
+
+ result = Update();
+
+ if (result == ResultCode.Success)
+ {
+ BroadcastOperationEvent();
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
index 52f3c9083c..865b1c0982 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
@@ -1,18 +1,23 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
abstract class SystemClockCore
{
- private SteadyClockCore _steadyClockCore;
- private SystemClockContext _context;
+ private SteadyClockCore _steadyClockCore;
+ private SystemClockContext _context;
+ private bool _isInitialized;
+ private SystemClockContextUpdateCallback _systemClockContextUpdateCallback;
public SystemClockCore(SteadyClockCore steadyClockCore)
{
_steadyClockCore = steadyClockCore;
_context = new SystemClockContext();
+ _isInitialized = false;
_context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
+ _systemClockContextUpdateCallback = null;
}
public virtual SteadyClockCore GetSteadyClockCore()
@@ -20,31 +25,115 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _steadyClockCore;
}
- public virtual ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+ public ResultCode GetCurrentTime(KThread thread, out long posixTime)
+ {
+ posixTime = 0;
+
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
+
+ ResultCode result = GetClockContext(thread, out SystemClockContext clockContext);
+
+ if (result == ResultCode.Success)
+ {
+ result = ResultCode.TimeMismatch;
+
+ if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
+ {
+ posixTime = clockContext.Offset + currentTimePoint.TimePoint;
+
+ result = 0;
+ }
+ }
+
+ return result;
+ }
+
+ public ResultCode SetCurrentTime(KThread thread, long posixTime)
+ {
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
+
+ SystemClockContext clockContext = new SystemClockContext()
+ {
+ Offset = posixTime - currentTimePoint.TimePoint,
+ SteadyTimePoint = currentTimePoint
+ };
+
+ ResultCode result = SetClockContext(clockContext);
+
+ if (result == ResultCode.Success)
+ {
+ result = Flush(clockContext);
+ }
+
+ return result;
+ }
+
+ public virtual ResultCode GetClockContext(KThread thread, out SystemClockContext context)
{
context = _context;
return ResultCode.Success;
}
- public virtual ResultCode SetSystemClockContext(SystemClockContext context)
+ public virtual ResultCode SetClockContext(SystemClockContext context)
{
_context = context;
return ResultCode.Success;
}
- public abstract ResultCode Flush(SystemClockContext context);
-
- public bool IsClockSetup(KThread thread)
+ protected virtual ResultCode Flush(SystemClockContext context)
{
- ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
+ if (_systemClockContextUpdateCallback == null)
+ {
+ return ResultCode.Success;
+ }
+
+ return _systemClockContextUpdateCallback.Update(context);
+ }
+
+ public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback)
+ {
+ _systemClockContextUpdateCallback = systemClockContextUpdateCallback;
+ }
+
+ public void RegisterOperationEvent(KWritableEvent writableEvent)
+ {
+ if (_systemClockContextUpdateCallback != null)
+ {
+ _systemClockContextUpdateCallback.RegisterOperationEvent(writableEvent);
+ }
+ }
+
+ public ResultCode SetSystemClockContext(SystemClockContext context)
+ {
+ ResultCode result = SetClockContext(context);
if (result == ResultCode.Success)
{
- SteadyClockCore steadyClockCore = GetSteadyClockCore();
+ result = Flush(context);
+ }
- SteadyClockTimePoint steadyClockTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
+ return result;
+ }
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
+
+ public bool IsClockSetup(KThread thread)
+ {
+ ResultCode result = GetClockContext(thread, out SystemClockContext context);
+
+ if (result == ResultCode.Success)
+ {
+ SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
index e5baba25d9..0650208276 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
@@ -4,22 +4,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class TickBasedSteadyClockCore : SteadyClockCore
{
- private static TickBasedSteadyClockCore _instance;
-
- public static TickBasedSteadyClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new TickBasedSteadyClockCore();
- }
-
- return _instance;
- }
- }
-
- private TickBasedSteadyClockCore() {}
+ public TickBasedSteadyClockCore() {}
public override SteadyClockTimePoint GetTimePoint(KThread thread)
{
@@ -29,7 +14,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
ClockSourceId = GetClockSourceId()
};
- TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ TimeSpanType ticksTimeSpan;
+
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
+ {
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ }
result.TimePoint = ticksTimeSpan.ToSeconds();
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
index 0055b5ead5..71fb45212f 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
[StructLayout(LayoutKind.Sequential)]
struct SteadyClockTimePoint
{
- public long TimePoint;
+ public long TimePoint;
public UInt128 ClockSourceId;
public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan)
@@ -30,5 +30,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return ResultCode.Overflow;
}
+
+ public static SteadyClockTimePoint GetRandom()
+ {
+ return new SteadyClockTimePoint
+ {
+ TimePoint = 0,
+ ClockSourceId = new UInt128(Guid.NewGuid().ToByteArray())
+ };
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
index 93579709f0..c336ad4196 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
@@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
private const long NanoSecondsPerSecond = 1000000000;
+ public static readonly TimeSpanType Zero = new TimeSpanType(0);
+
public long NanoSeconds;
public TimeSpanType(long nanoSeconds)
diff --git a/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
index cb10da47bb..8ec55c1594 100644
--- a/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:m")] // 9.0.0+
+ [Service("time:p")] // 9.0.0+
class IPowerStateRequestHandler : IpcService
{
public IPowerStateRequestHandler(ServiceCtx context) { }
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs
new file mode 100644
index 0000000000..605cbbbd7a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs
@@ -0,0 +1,182 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
+using Ryujinx.HLE.HOS.Services.Settings;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.StaticService;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ [Service("time:a", TimePermissions.Admin)]
+ [Service("time:r", TimePermissions.Repair)]
+ [Service("time:u", TimePermissions.User)]
+ class IStaticServiceForGlue : IpcService
+ {
+ private IStaticServiceForPsc _inner;
+ private TimePermissions _permissions;
+
+ public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions)
+ {
+ _permissions = permissions;
+ _inner = new IStaticServiceForPsc(context, permissions);
+ }
+
+ [Command(0)]
+ // GetStandardUserSystemClock() -> object
+ public ResultCode GetStandardUserSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardUserSystemClock(context);
+ }
+
+ [Command(1)]
+ // GetStandardNetworkSystemClock() -> object
+ public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardNetworkSystemClock(context);
+ }
+
+ [Command(2)]
+ // GetStandardSteadyClock() -> object
+ public ResultCode GetStandardSteadyClock(ServiceCtx context)
+ {
+ return _inner.GetStandardSteadyClock(context);
+ }
+
+ [Command(3)]
+ // GetTimeZoneService() -> object
+ public ResultCode GetTimeZoneService(ServiceCtx context)
+ {
+ MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0));
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)]
+ // GetStandardLocalSystemClock() -> object
+ public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardLocalSystemClock(context);
+ }
+
+ [Command(5)] // 4.0.0+
+ // GetEphemeralNetworkSystemClock() -> object
+ public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context)
+ {
+ return _inner.GetEphemeralNetworkSystemClock(context);
+ }
+
+ [Command(20)] // 6.0.0+
+ // GetSharedMemoryNativeHandle() -> handle
+ public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context)
+ {
+ return _inner.GetSharedMemoryNativeHandle(context);
+ }
+
+ [Command(50)] // 4.0.0+
+ // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset)
+ public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context)
+ {
+ if ((_permissions & TimePermissions.BypassUninitialized) == 0)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ TimeSpanType internalOffset = context.RequestData.ReadStruct();
+
+ // TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds())
+
+ return ResultCode.Success;
+ }
+
+ [Command(51)] // 9.0.0+
+ // GetStandardSteadyClockRtcValue() -> u64
+ public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context)
+ {
+ ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(rtcValue);
+ }
+
+ return result;
+ }
+
+ [Command(100)]
+ // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
+ public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+ {
+ return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context);
+ }
+
+ [Command(101)]
+ // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
+ public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+ {
+ return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context);
+ }
+
+ [Command(102)] // 5.0.0+
+ // GetStandardUserSystemClockInitialYear() -> u32
+ public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context)
+ {
+ if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear))
+ {
+ throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!");
+ }
+
+ context.ResponseData.Write((int)standardUserSystemClockInitialYear);
+
+ return ResultCode.Success;
+ }
+
+ [Command(200)] // 3.0.0+
+ // IsStandardNetworkSystemClockAccuracySufficient() -> bool
+ public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
+ {
+ return _inner.IsStandardNetworkSystemClockAccuracySufficient(context);
+ }
+
+ [Command(201)] // 6.0.0+
+ // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context)
+ {
+ return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context);
+ }
+
+ [Command(300)] // 4.0.0+
+ // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
+ public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
+ {
+ return _inner.CalculateMonotonicSystemClockBaseTimePoint(context);
+ }
+
+ [Command(400)] // 4.0.0+
+ // GetClockSnapshot(u8) -> buffer
+ public ResultCode GetClockSnapshot(ServiceCtx context)
+ {
+ return _inner.GetClockSnapshot(context);
+ }
+
+ [Command(401)] // 4.0.0+
+ // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer
+ public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context)
+ {
+ return _inner.GetClockSnapshotFromSystemClockContext(context);
+ }
+
+ [Command(500)] // 4.0.0+
+ // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType
+ public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
+ {
+ return _inner.CalculateStandardUserSystemClockDifferenceByUser(context);
+ }
+
+ [Command(501)] // 4.0.0+
+ // CalculateSpanBetween(buffer, buffer) -> nn::TimeSpanType
+ public ResultCode CalculateSpanBetween(ServiceCtx context)
+ {
+ return _inner.CalculateSpanBetween(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs
similarity index 64%
rename from Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
rename to Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs
index 0cfdebcfbe..5ea3910f3b 100644
--- a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs
@@ -12,28 +12,30 @@ using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:a", TimePermissions.Applet)]
[Service("time:s", TimePermissions.System)]
- [Service("time:u", TimePermissions.User)]
- [Service("time:p", TimePermissions.System)] // 9.0.0+ - TODO: Fix the permission.
- class IStaticService : IpcService
+ [Service("time:su", TimePermissions.SystemUpdate)]
+ class IStaticServiceForPsc : IpcService
{
+ private TimeManager _timeManager;
private TimePermissions _permissions;
private int _timeSharedMemoryNativeHandle = 0;
- private static readonly DateTime StartupDate = DateTime.UtcNow;
+ public IStaticServiceForPsc(ServiceCtx context, TimePermissions permissions) : this(TimeManager.Instance, permissions) {}
- public IStaticService(ServiceCtx context, TimePermissions permissions)
+ public IStaticServiceForPsc(TimeManager manager, TimePermissions permissions)
{
_permissions = permissions;
+ _timeManager = manager;
}
[Command(0)]
// GetStandardUserSystemClock() -> object
public ResultCode GetStandardUserSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardUserSystemClockCore.Instance, (_permissions & TimePermissions.UserSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardUserSystemClock,
+ (_permissions & TimePermissions.UserSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -42,7 +44,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardNetworkSystemClock() -> object
public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance, (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock,
+ (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -51,7 +55,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardSteadyClock() -> object
public ResultCode GetStandardSteadyClock(ServiceCtx context)
{
- MakeObject(context, new ISteadyClock());
+ MakeObject(context, new ISteadyClock(_timeManager.StandardSteadyClock,
+ (_permissions & TimePermissions.SteadyClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -60,7 +66,8 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetTimeZoneService() -> object
public ResultCode GetTimeZoneService(ServiceCtx context)
{
- MakeObject(context, new ITimeZoneService());
+ MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager,
+ (_permissions & TimePermissions.TimeZoneWritableMask) != 0));
return ResultCode.Success;
}
@@ -69,7 +76,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardLocalSystemClock() -> object
public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardLocalSystemClockCore.Instance, (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardLocalSystemClock,
+ (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -78,7 +87,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetEphemeralNetworkSystemClock() -> object
public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance, false));
+ MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock,
+ (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -89,7 +100,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
if (_timeSharedMemoryNativeHandle == 0)
{
- if (context.Process.HandleTable.GenerateHandle(context.Device.System.TimeSharedMem, out _timeSharedMemoryNativeHandle) != KernelResult.Success)
+ if (context.Process.HandleTable.GenerateHandle(_timeManager.SharedMemory.GetSharedMemory(), out _timeSharedMemoryNativeHandle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
@@ -100,11 +111,34 @@ namespace Ryujinx.HLE.HOS.Services.Time
return ResultCode.Success;
}
+ [Command(50)] // 4.0.0+
+ // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset)
+ public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(51)] // 9.0.0+
+ // GetStandardSteadyClockRtcValue() -> u64
+ public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
+ }
+
[Command(100)]
// IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
- context.ResponseData.Write(StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled());
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write(userClock.IsAutomaticCorrectionEnabled());
return ResultCode.Success;
}
@@ -113,6 +147,14 @@ namespace Ryujinx.HLE.HOS.Services.Time
// SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
+ SteadyClockCore steadyClock = _timeManager.StandardSteadyClock;
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized() || !steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0)
{
return ResultCode.PermissionDenied;
@@ -120,14 +162,50 @@ namespace Ryujinx.HLE.HOS.Services.Time
bool autoCorrectionEnabled = context.RequestData.ReadBoolean();
- return StandardUserSystemClockCore.Instance.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
+ ResultCode result = userClock.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
+
+ if (result == ResultCode.Success)
+ {
+ _timeManager.SharedMemory.SetAutomaticCorrectionEnabled(autoCorrectionEnabled);
+
+ SteadyClockTimePoint currentTimePoint = userClock.GetSteadyClockCore().GetCurrentTimePoint(context.Thread);
+
+ userClock.SetAutomaticCorrectionUpdatedTime(currentTimePoint);
+ userClock.SignalAutomaticCorrectionEvent();
+ }
+
+ return result;
+ }
+
+ [Command(102)] // 5.0.0+
+ // GetStandardUserSystemClockInitialYear() -> u32
+ public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
}
[Command(200)] // 3.0.0+
// IsStandardNetworkSystemClockAccuracySufficient() -> bool
public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
{
- context.ResponseData.Write(StandardNetworkSystemClockCore.Instance.IsStandardNetworkSystemClockAccuracySufficient(context.Thread));
+ context.ResponseData.Write(_timeManager.StandardNetworkSystemClock.IsStandardNetworkSystemClockAccuracySufficient(context.Thread));
+
+ return ResultCode.Success;
+ }
+
+ [Command(201)] // 6.0.0+
+ // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context)
+ {
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(userClock.GetAutomaticCorrectionUpdatedTime());
return ResultCode.Success;
}
@@ -136,8 +214,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
{
+ SteadyClockCore steadyClock = _timeManager.StandardSteadyClock;
+
+ if (!steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
SystemClockContext otherContext = context.RequestData.ReadStruct();
- SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
+ SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(context.Thread);
ResultCode result = ResultCode.TimeMismatch;
@@ -148,7 +233,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
context.ResponseData.Write(baseTimePoint);
- result = 0;
+ result = ResultCode.Success;
}
return result;
@@ -160,11 +245,11 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
byte type = context.RequestData.ReadByte();
- ResultCode result = StandardUserSystemClockCore.Instance.GetSystemClockContext(context.Thread, out SystemClockContext userContext);
+ ResultCode result = _timeManager.StandardUserSystemClock.GetClockContext(context.Thread, out SystemClockContext userContext);
if (result == ResultCode.Success)
{
- result = StandardNetworkSystemClockCore.Instance.GetSystemClockContext(context.Thread, out SystemClockContext networkContext);
+ result = _timeManager.StandardNetworkSystemClock.GetClockContext(context.Thread, out SystemClockContext networkContext);
if (result == ResultCode.Success)
{
@@ -205,7 +290,6 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType
public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
{
-
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]);
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]);
TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset);
@@ -259,25 +343,32 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
clockSnapshot = new ClockSnapshot();
- SteadyClockCore steadyClockCore = StandardSteadyClockCore.Instance;
+ SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock;
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
- clockSnapshot.IsAutomaticCorrectionEnabled = StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled();
+ clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled();
clockSnapshot.UserContext = userContext;
clockSnapshot.NetworkContext = networkContext;
- char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
+ ResultCode result = _timeManager.TimeZone.Manager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ char[] tzName = deviceLocationName.ToCharArray();
char[] locationName = new char[0x24];
Array.Copy(tzName, locationName, tzName.Length);
clockSnapshot.LocationName = locationName;
- ResultCode result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
+ result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
if (result == ResultCode.Success)
{
- result = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo);
+ result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo);
if (result == ResultCode.Success)
{
@@ -289,7 +380,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
clockSnapshot.NetworkTime = 0;
}
- result = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo);
+ result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo);
if (result == ResultCode.Success)
{
diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
index 514e901ea9..a897b3f78f 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
@@ -1,8 +1,219 @@
-namespace Ryujinx.HLE.HOS.Services.Time
+using Ryujinx.Common;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:su")] // 9.0.0+
+ [Service("time:m")] // 9.0.0+
class ITimeServiceManager : IpcService
{
- public ITimeServiceManager(ServiceCtx context) { }
+ private TimeManager _timeManager;
+ private int _automaticCorrectionEvent;
+
+ public ITimeServiceManager(ServiceCtx context)
+ {
+ _timeManager = TimeManager.Instance;
+ _automaticCorrectionEvent = 0;
+ }
+
+ [Command(0)]
+ // GetUserStaticService() -> object
+ public ResultCode GetUserStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User));
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // GetAdminStaticService() -> object
+ public ResultCode GetAdminStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin));
+
+ return ResultCode.Success;
+ }
+
+ [Command(6)]
+ // GetRepairStaticService() -> object
+ public ResultCode GetRepairStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair));
+
+ return ResultCode.Success;
+ }
+
+ [Command(9)]
+ // GetManufactureStaticService() -> object
+ public ResultCode GetManufactureStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture));
+
+ return ResultCode.Success;
+ }
+
+ [Command(10)]
+ // SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected)
+ public ResultCode SetupStandardSteadyClock(ServiceCtx context)
+ {
+ UInt128 clockSourceId = context.RequestData.ReadStruct();
+ TimeSpanType setupValue = context.RequestData.ReadStruct();
+ TimeSpanType internalOffset = context.RequestData.ReadStruct();
+ TimeSpanType testOffset = context.RequestData.ReadStruct();
+ bool isRtcResetDetected = context.RequestData.ReadBoolean();
+
+ _timeManager.SetupStandardSteadyClock(context.Thread, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected);
+
+ return ResultCode.Success;
+ }
+
+ [Command(11)]
+ // SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time)
+ public ResultCode SetupStandardLocalSystemClock(ServiceCtx context)
+ {
+ SystemClockContext clockContext = context.RequestData.ReadStruct();
+ long posixTime = context.RequestData.ReadInt64();
+
+ _timeManager.SetupStandardLocalSystemClock(context.Thread, clockContext, posixTime);
+
+ return ResultCode.Success;
+ }
+
+ [Command(12)]
+ // SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy)
+ public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context)
+ {
+ SystemClockContext clockContext = context.RequestData.ReadStruct();
+ TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct();
+
+ _timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy);
+
+ return ResultCode.Success;
+ }
+
+ [Command(13)]
+ // SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint)
+ public ResultCode SetupStandardUserSystemClock(ServiceCtx context)
+ {
+ bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean();
+
+ context.RequestData.BaseStream.Position += 7;
+
+ SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct();
+
+ _timeManager.SetupStandardUserSystemClock(context.Thread, isAutomaticCorrectionEnabled, steadyClockTimePoint);
+
+ return ResultCode.Success;
+ }
+
+ [Command(14)]
+ // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer timezone_binary)
+ public ResultCode SetupTimeZoneManager(ServiceCtx context)
+ {
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+ SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct();
+ uint totalLocationNameCount = context.RequestData.ReadUInt32();
+ UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct();
+
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ _timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(15)]
+ // SetupEphemeralNetworkSystemClock()
+ public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context)
+ {
+ _timeManager.SetupEphemeralNetworkSystemClock();
+
+ return ResultCode.Success;
+ }
+
+ [Command(50)]
+ // Unknown50() -> handle
+ public ResultCode Unknown50(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(51)]
+ // Unknown51() -> handle
+ public ResultCode Unknown51(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(52)]
+ // Unknown52() -> handle
+ public ResultCode Unknown52(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(60)]
+ // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context)
+ {
+ if (_automaticCorrectionEvent == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent);
+
+ return ResultCode.Success;
+ }
+
+ [Command(100)]
+ // SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset)
+ public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context)
+ {
+ TimeSpanType rtcOffset = context.RequestData.ReadStruct();
+
+ _timeManager.SetStandardSteadyClockRtcOffset(context.Thread, rtcOffset);
+
+ return ResultCode.Success;
+ }
+
+ [Command(200)]
+ // GetAlarmRegistrationEvent() -> handle
+ public ResultCode GetAlarmRegistrationEvent(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(201)]
+ // UpdateSteadyAlarms()
+ public ResultCode UpdateSteadyAlarms(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(202)]
+ // TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot)
+ public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
index ed7130f364..1ef895c258 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
@@ -9,6 +9,7 @@
PermissionDenied = (1 << ErrorCodeShift) | ModuleId,
TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
+ UninitializedClock = (103 << ErrorCodeShift) | ModuleId,
TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
Overflow = (201 << ErrorCodeShift) | ModuleId,
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
index 31f119df54..bf6a4fd10d 100644
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
@@ -5,42 +5,78 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
class ISteadyClock : IpcService
{
+ private SteadyClockCore _steadyClock;
+ private bool _writePermission;
+ private bool _bypassUninitializedClock;
+
+ public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock)
+ {
+ _steadyClock = steadyClock;
+ _writePermission = writePermission;
+ _bypassUninitializedClock = bypassUninitializedClock;
+ }
+
[Command(0)]
// GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint
public ResultCode GetCurrentTimePoint(ServiceCtx context)
{
- SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(context.Thread);
context.ResponseData.WriteStruct(currentTimePoint);
return ResultCode.Success;
}
- [Command(1)]
+ [Command(2)]
// GetTestOffset() -> nn::TimeSpanType
public ResultCode GetTestOffset(ServiceCtx context)
{
- context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetTestOffset());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(_steadyClock.GetTestOffset());
return ResultCode.Success;
}
- [Command(2)]
+ [Command(3)]
// SetTestOffset(nn::TimeSpanType)
public ResultCode SetTestOffset(ServiceCtx context)
{
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
TimeSpanType testOffset = context.RequestData.ReadStruct();
- StandardSteadyClockCore.Instance.SetTestOffset(testOffset);
+ _steadyClock.SetTestOffset(testOffset);
- return 0;
+ return ResultCode.Success;
}
[Command(100)] // 2.0.0+
// GetRtcValue() -> u64
public ResultCode GetRtcValue(ServiceCtx context)
{
- ResultCode result = StandardSteadyClockCore.Instance.GetRtcValue(out ulong rtcValue);
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue);
if (result == ResultCode.Success)
{
@@ -54,7 +90,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// IsRtcResetDetected() -> bool
public ResultCode IsRtcResetDetected(ServiceCtx context)
{
- context.ResponseData.Write(StandardSteadyClockCore.Instance.IsRtcResetDetected());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write(_steadyClock.IsRtcResetDetected());
return ResultCode.Success;
}
@@ -63,7 +104,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetSetupResultValue() -> u32
public ResultCode GetSetupResultValue(ServiceCtx context)
{
- context.ResponseData.Write((uint)StandardSteadyClockCore.Instance.GetSetupResultValue());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue());
return ResultCode.Success;
}
@@ -72,7 +118,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetInternalOffset() -> nn::TimeSpanType
public ResultCode GetInternalOffset(ServiceCtx context)
{
- context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetInternalOffset());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset());
return ResultCode.Success;
}
@@ -81,9 +132,19 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// SetInternalOffset(nn::TimeSpanType)
public ResultCode SetInternalOffset(ServiceCtx context)
{
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
TimeSpanType internalOffset = context.RequestData.ReadStruct();
- StandardSteadyClockCore.Instance.SetInternalOffset(internalOffset);
+ _steadyClock.SetInternalOffset(internalOffset);
return ResultCode.Success;
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
index 0d86617759..d5b21f8c95 100644
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
@@ -1,5 +1,9 @@
using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
@@ -7,34 +11,31 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
private SystemClockCore _clockCore;
private bool _writePermission;
+ private bool _bypassUninitializedClock;
+ private int _operationEventReadableHandle;
- public ISystemClock(SystemClockCore clockCore, bool writePermission)
+ public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock)
{
- _clockCore = clockCore;
- _writePermission = writePermission;
+ _clockCore = clockCore;
+ _writePermission = writePermission;
+ _bypassUninitializedClock = bypassUninitializedClock;
+ _operationEventReadableHandle = 0;
}
[Command(0)]
// GetCurrentTime() -> nn::time::PosixTime
public ResultCode GetCurrentTime(ServiceCtx context)
{
- SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
- SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
- ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
+ ResultCode result = _clockCore.GetCurrentTime(context.Thread, out long posixTime);
if (result == ResultCode.Success)
{
- result = ResultCode.TimeMismatch;
-
- if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
- {
- long posixTime = clockContext.Offset + currentTimePoint.TimePoint;
-
- context.ResponseData.Write(posixTime);
-
- result = 0;
- }
+ context.ResponseData.Write(posixTime);
}
return result;
@@ -49,31 +50,26 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied;
}
- long posixTime = context.RequestData.ReadInt64();
- SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
- SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
-
- SystemClockContext clockContext = new SystemClockContext()
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
{
- Offset = posixTime - currentTimePoint.TimePoint,
- SteadyTimePoint = currentTimePoint
- };
-
- ResultCode result = _clockCore.SetSystemClockContext(clockContext);
-
- if (result == ResultCode.Success)
- {
- result = _clockCore.Flush(clockContext);
+ return ResultCode.UninitializedClock;
}
- return result;
+ long posixTime = context.RequestData.ReadInt64();
+
+ return _clockCore.SetCurrentTime(context.Thread, posixTime);
}
[Command(2)]
- // GetSystemClockContext() -> nn::time::SystemClockContext
+ // GetClockContext() -> nn::time::SystemClockContext
public ResultCode GetSystemClockContext(ServiceCtx context)
{
- ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ ResultCode result = _clockCore.GetClockContext(context.Thread, out SystemClockContext clockContext);
if (result == ResultCode.Success)
{
@@ -84,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
}
[Command(3)]
- // SetSystemClockContext(nn::time::SystemClockContext)
+ // SetClockContext(nn::time::SystemClockContext)
public ResultCode SetSystemClockContext(ServiceCtx context)
{
if (!_writePermission)
@@ -92,16 +88,37 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied;
}
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
SystemClockContext clockContext = context.RequestData.ReadStruct();
ResultCode result = _clockCore.SetSystemClockContext(clockContext);
- if (result == ResultCode.Success)
+ return result;
+ }
+
+ [Command(4)] // 9.0.0+
+ // GetOperationEventReadableHandle() -> handle
+ public ResultCode GetOperationEventReadableHandle(ServiceCtx context)
+ {
+ if (_operationEventReadableHandle == 0)
{
- result = _clockCore.Flush(clockContext);
+ KEvent kEvent = new KEvent(context.Device.System);
+
+ _clockCore.RegisterOperationEvent(kEvent.WritableEvent);
+
+ if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
}
- return result;
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle);
+
+ return ResultCode.Success;
}
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs
deleted file mode 100644
index c65107dfe4..0000000000
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.HOS.Services.Time.TimeZone;
-using System;
-using System.Text;
-
-namespace Ryujinx.HLE.HOS.Services.Time.StaticService
-{
- class ITimeZoneService : IpcService
- {
- public ITimeZoneService() { }
-
- [Command(0)]
- // GetDeviceLocationName() -> nn::time::LocationName
- public ResultCode GetDeviceLocationName(ServiceCtx context)
- {
- char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
-
- int padding = 0x24 - tzName.Length;
-
- if (padding < 0)
- {
- return ResultCode.LocationNameTooLong;
- }
-
- context.ResponseData.Write(tzName);
-
- for (int index = 0; index < padding; index++)
- {
- context.ResponseData.Write((byte)0);
- }
-
- return ResultCode.Success;
- }
-
- [Command(1)]
- // SetDeviceLocationName(nn::time::LocationName)
- public ResultCode SetDeviceLocationName(ServiceCtx context)
- {
- string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
-
- return TimeZoneManager.Instance.SetDeviceLocationName(locationName);
- }
-
- [Command(2)]
- // GetTotalLocationNameCount() -> u32
- public ResultCode GetTotalLocationNameCount(ServiceCtx context)
- {
- context.ResponseData.Write(TimeZoneManager.Instance.GetTotalLocationNameCount());
-
- return ResultCode.Success;
- }
-
- [Command(3)]
- // LoadLocationNameList(u32 index) -> (u32 outCount, buffer)
- public ResultCode LoadLocationNameList(ServiceCtx context)
- {
- uint index = context.RequestData.ReadUInt32();
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferSize = context.Request.ReceiveBuff[0].Size;
-
- ResultCode errorCode = TimeZoneManager.Instance.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
-
- if (errorCode == 0)
- {
- uint offset = 0;
-
- foreach (string locationName in locationNameArray)
- {
- int padding = 0x24 - locationName.Length;
-
- if (padding < 0)
- {
- return ResultCode.LocationNameTooLong;
- }
-
- context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
- MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
-
- offset += 0x24;
- }
-
- context.ResponseData.Write((uint)locationNameArray.Length);
- }
-
- return errorCode;
- }
-
- [Command(4)]
- // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer
- public ResultCode LoadTimeZoneRule(ServiceCtx context)
- {
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferSize = context.Request.ReceiveBuff[0].Size;
-
- if (bufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
-
- string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
-
- ResultCode resultCode = TimeZoneManager.Instance.LoadTimeZoneRules(out TimeZoneRule rules, locationName);
-
- // Write TimeZoneRule if success
- if (resultCode == 0)
- {
- MemoryHelper.Write(context.Memory, bufferPosition, rules);
- }
-
- return resultCode;
- }
-
- [Command(100)]
- // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
- public ResultCode ToCalendarTime(ServiceCtx context)
- {
- long posixTime = context.RequestData.ReadInt64();
- long bufferPosition = context.Request.SendBuff[0].Position;
- long bufferSize = context.Request.SendBuff[0].Size;
-
- if (bufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
- TimeZoneRule rules = MemoryHelper.Read(context.Memory, bufferPosition);
-
- ResultCode resultCode = TimeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
-
- if (resultCode == 0)
- {
- context.ResponseData.WriteStruct(calendar);
- }
-
- return resultCode;
- }
-
- [Command(101)]
- // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
- public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
- {
- long posixTime = context.RequestData.ReadInt64();
-
- ResultCode resultCode = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
-
- if (resultCode == 0)
- {
- context.ResponseData.WriteStruct(calendar);
- }
-
- return resultCode;
- }
-
- [Command(201)]
- // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer)
- public ResultCode ToPosixTime(ServiceCtx context)
- {
- long inBufferPosition = context.Request.SendBuff[0].Position;
- long inBufferSize = context.Request.SendBuff[0].Size;
-
- CalendarTime calendarTime = context.RequestData.ReadStruct();
-
- if (inBufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
- TimeZoneRule rules = MemoryHelper.Read(context.Memory, inBufferPosition);
-
- ResultCode resultCode = TimeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
-
- if (resultCode == 0)
- {
- long outBufferPosition = context.Request.RecvListBuff[0].Position;
- long outBufferSize = context.Request.RecvListBuff[0].Size;
-
- context.Memory.WriteInt64(outBufferPosition, posixTime);
- context.ResponseData.Write(1);
- }
-
- return resultCode;
- }
-
- [Command(202)]
- // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer)
- public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
- {
- CalendarTime calendarTime = context.RequestData.ReadStruct();
-
- ResultCode resultCode = TimeZoneManager.Instance.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
-
- if (resultCode == 0)
- {
- long outBufferPosition = context.Request.RecvListBuff[0].Position;
- long outBufferSize = context.Request.RecvListBuff[0].Size;
-
- context.Memory.WriteInt64(outBufferPosition, posixTime);
-
- // There could be only one result on one calendar as leap seconds aren't supported.
- context.ResponseData.Write(1);
- }
-
- return resultCode;
- }
- }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs
new file mode 100644
index 0000000000..7acb025726
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs
@@ -0,0 +1,142 @@
+using ARMeilleure.Memory;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using System;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time.StaticService
+{
+ class ITimeZoneServiceForGlue : IpcService
+ {
+ private TimeZoneContentManager _timeZoneContentManager;
+ private ITimeZoneServiceForPsc _inner;
+ private bool _writePermission;
+
+ public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission)
+ {
+ _timeZoneContentManager = timeZoneContentManager;
+ _writePermission = writePermission;
+ _inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission);
+ }
+
+ [Command(0)]
+ // GetDeviceLocationName() -> nn::time::LocationName
+ public ResultCode GetDeviceLocationName(ServiceCtx context)
+ {
+ return _inner.GetDeviceLocationName(context);
+ }
+
+ [Command(1)]
+ // SetDeviceLocationName(nn::time::LocationName)
+ public ResultCode SetDeviceLocationName(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ return _timeZoneContentManager.SetDeviceLocationName(locationName);
+ }
+
+ [Command(2)]
+ // GetTotalLocationNameCount() -> u32
+ public ResultCode GetTotalLocationNameCount(ServiceCtx context)
+ {
+ return _inner.GetTotalLocationNameCount(context);
+ }
+
+ [Command(3)]
+ // LoadLocationNameList(u32 index) -> (u32 outCount, buffer)
+ public ResultCode LoadLocationNameList(ServiceCtx context)
+ {
+ uint index = context.RequestData.ReadUInt32();
+ long bufferPosition = context.Request.ReceiveBuff[0].Position;
+ long bufferSize = context.Request.ReceiveBuff[0].Size;
+
+ ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
+
+ if (errorCode == 0)
+ {
+ uint offset = 0;
+
+ foreach (string locationName in locationNameArray)
+ {
+ int padding = 0x24 - locationName.Length;
+
+ if (padding < 0)
+ {
+ return ResultCode.LocationNameTooLong;
+ }
+
+ context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
+ MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
+
+ offset += 0x24;
+ }
+
+ context.ResponseData.Write((uint)locationNameArray.Length);
+ }
+
+ return errorCode;
+ }
+
+ [Command(4)]
+ // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer
+ public ResultCode LoadTimeZoneRule(ServiceCtx context)
+ {
+ long bufferPosition = context.Request.ReceiveBuff[0].Position;
+ long bufferSize = context.Request.ReceiveBuff[0].Size;
+
+ if (bufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
+
+ // Write TimeZoneRule if success
+ if (resultCode == ResultCode.Success)
+ {
+ MemoryHelper.Write(context.Memory, bufferPosition, rules);
+ }
+
+ return resultCode;
+ }
+
+ [Command(100)]
+ // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTime(ServiceCtx context)
+ {
+ return _inner.ToCalendarTime(context);
+ }
+
+ [Command(101)]
+ // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
+ {
+ return _inner.ToCalendarTimeWithMyRule(context);
+ }
+
+ [Command(201)]
+ // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer)
+ public ResultCode ToPosixTime(ServiceCtx context)
+ {
+ return _inner.ToPosixTime(context);
+ }
+
+ [Command(202)]
+ // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer)
+ public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
+ {
+ return _inner.ToPosixTimeWithMyRule(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs
new file mode 100644
index 0000000000..ed31fe7c53
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs
@@ -0,0 +1,294 @@
+using ARMeilleure.Memory;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time.StaticService
+{
+ class ITimeZoneServiceForPsc : IpcService
+ {
+ private TimeZoneManager _timeZoneManager;
+ private bool _writePermission;
+
+ public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission)
+ {
+ _timeZoneManager = timeZoneManager;
+ _writePermission = writePermission;
+ }
+
+ [Command(0)]
+ // GetDeviceLocationName() -> nn::time::LocationName
+ public ResultCode GetDeviceLocationName(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result == ResultCode.Success)
+ {
+ WriteLocationName(context, deviceLocationName);
+ }
+
+ return result;
+ }
+
+ [Command(1)]
+ // SetDeviceLocationName(nn::time::LocationName)
+ public ResultCode SetDeviceLocationName(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(2)]
+ // GetTotalLocationNameCount() -> u32
+ public ResultCode GetTotalLocationNameCount(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(totalLocationNameCount);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // LoadLocationNameList(u32 index) -> (u32 outCount, buffer)
+ public ResultCode LoadLocationNameList(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(4)]
+ // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer
+ public ResultCode LoadTimeZoneRule(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(5)] // 2.0.0+
+ // GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion
+ public ResultCode GetTimeZoneRuleVersion(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.WriteStruct(timeZoneRuleVersion);
+ }
+
+ return result;
+ }
+
+ [Command(6)] // 5.0.0+
+ // GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint)
+ public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result == ResultCode.Success)
+ {
+ result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint);
+
+ if (result == ResultCode.Success)
+ {
+ WriteLocationName(context, deviceLocationName);
+
+ // Skip padding
+ context.ResponseData.BaseStream.Position += 0x4;
+
+ context.ResponseData.WriteStruct(timeZoneUpdateTimePoint);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(7)] // 9.0.0+
+ // SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer timeZoneBinary)
+ public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ ResultCode result;
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
+ }
+
+ return result;
+ }
+
+ [Command(8)] // 9.0.0+
+ // ParseTimeZoneBinary(buffer timeZoneBinary) -> buffer
+ public ResultCode ParseTimeZoneBinary(ServiceCtx context)
+ {
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ long timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position;
+ long timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size;
+
+ if (timeZoneRuleBufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ ResultCode result;
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
+
+ if (result == ResultCode.Success)
+ {
+ MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(20)] // 9.0.0+
+ // GetDeviceLocationNameOperationEventReadableHandle() -> handle
+ public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(100)]
+ // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTime(ServiceCtx context)
+ {
+ long posixTime = context.RequestData.ReadInt64();
+ long bufferPosition = context.Request.SendBuff[0].Position;
+ long bufferSize = context.Request.SendBuff[0].Size;
+
+ if (bufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ TimeZoneRule rules = MemoryHelper.Read(context.Memory, bufferPosition);
+
+ ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
+
+ if (resultCode == 0)
+ {
+ context.ResponseData.WriteStruct(calendar);
+ }
+
+ return resultCode;
+ }
+
+ [Command(101)]
+ // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
+ {
+ long posixTime = context.RequestData.ReadInt64();
+
+ ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
+
+ if (resultCode == 0)
+ {
+ context.ResponseData.WriteStruct(calendar);
+ }
+
+ return resultCode;
+ }
+
+ [Command(201)]
+ // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer)
+ public ResultCode ToPosixTime(ServiceCtx context)
+ {
+ long inBufferPosition = context.Request.SendBuff[0].Position;
+ long inBufferSize = context.Request.SendBuff[0].Size;
+
+ CalendarTime calendarTime = context.RequestData.ReadStruct();
+
+ if (inBufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ TimeZoneRule rules = MemoryHelper.Read(context.Memory, inBufferPosition);
+
+ ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
+
+ if (resultCode == 0)
+ {
+ long outBufferPosition = context.Request.RecvListBuff[0].Position;
+ long outBufferSize = context.Request.RecvListBuff[0].Size;
+
+ context.Memory.WriteInt64(outBufferPosition, posixTime);
+ context.ResponseData.Write(1);
+ }
+
+ return resultCode;
+ }
+
+ [Command(202)]
+ // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer)
+ public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
+ {
+ CalendarTime calendarTime = context.RequestData.ReadStruct();
+
+ ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
+
+ if (resultCode == 0)
+ {
+ long outBufferPosition = context.Request.RecvListBuff[0].Position;
+ long outBufferSize = context.Request.RecvListBuff[0].Size;
+
+ context.Memory.WriteInt64(outBufferPosition, posixTime);
+
+ // There could be only one result on one calendar as leap seconds aren't supported.
+ context.ResponseData.Write(1);
+ }
+
+ return resultCode;
+ }
+
+ private void WriteLocationName(ServiceCtx context, string locationName)
+ {
+ char[] locationNameArray = locationName.ToCharArray();
+
+ int padding = 0x24 - locationNameArray.Length;
+
+ Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)");
+
+ context.ResponseData.Write(locationNameArray);
+
+ for (int index = 0; index < padding; index++)
+ {
+ context.ResponseData.Write((byte)0);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs
new file mode 100644
index 0000000000..7c5d716399
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs
@@ -0,0 +1,184 @@
+using System;
+using System.IO;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class TimeManager
+ {
+ private static TimeManager _instance;
+
+ public static TimeManager Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new TimeManager();
+ }
+
+ return _instance;
+ }
+ }
+
+ public StandardSteadyClockCore StandardSteadyClock { get; }
+ public TickBasedSteadyClockCore TickBasedSteadyClock { get; }
+ public StandardLocalSystemClockCore StandardLocalSystemClock { get; }
+ public StandardNetworkSystemClockCore StandardNetworkSystemClock { get; }
+ public StandardUserSystemClockCore StandardUserSystemClock { get; }
+ public TimeZoneContentManager TimeZone { get; }
+ public EphemeralNetworkSystemClockCore EphemeralNetworkSystemClock { get; }
+ public TimeSharedMemory SharedMemory { get; }
+ public LocalSystemClockContextWriter LocalClockContextWriter { get; }
+ public NetworkSystemClockContextWriter NetworkClockContextWriter { get; }
+ public EphemeralNetworkSystemClockContextWriter EphemeralClockContextWriter { get; }
+
+ // TODO: 9.0.0+ power states and alarms
+
+ public TimeManager()
+ {
+ StandardSteadyClock = new StandardSteadyClockCore();
+ TickBasedSteadyClock = new TickBasedSteadyClockCore();
+ StandardLocalSystemClock = new StandardLocalSystemClockCore(StandardSteadyClock);
+ StandardNetworkSystemClock = new StandardNetworkSystemClockCore(StandardSteadyClock);
+ StandardUserSystemClock = new StandardUserSystemClockCore(StandardLocalSystemClock, StandardNetworkSystemClock);
+ TimeZone = new TimeZoneContentManager();
+ EphemeralNetworkSystemClock = new EphemeralNetworkSystemClockCore(StandardSteadyClock);
+ SharedMemory = new TimeSharedMemory();
+ LocalClockContextWriter = new LocalSystemClockContextWriter(SharedMemory);
+ NetworkClockContextWriter = new NetworkSystemClockContextWriter(SharedMemory);
+ EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter();
+ }
+
+ public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
+ {
+ SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize);
+
+ // Here we use system on purpose as device. System isn't initialized at this point.
+ StandardUserSystemClock.CreateAutomaticCorrectionEvent(system);
+ }
+
+ public void InitializeTimeZone(Switch device)
+ {
+ TimeZone.Initialize(this, device);
+ }
+
+
+ public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected)
+ {
+ SetupInternalStandardSteadyClock(clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected);
+
+ TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread);
+
+ SharedMemory.SetupStandardSteadyClock(thread, clockSourceId, currentTimePoint);
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ private void SetupInternalStandardSteadyClock(UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected)
+ {
+ StandardSteadyClock.SetClockSourceId(clockSourceId);
+ StandardSteadyClock.SetSetupValue(setupValue);
+ StandardSteadyClock.SetInternalOffset(internalOffset);
+ StandardSteadyClock.SetTestOffset(testOffset);
+
+ if (isRtcResetDetected)
+ {
+ StandardSteadyClock.SetRtcReset();
+ }
+
+ StandardSteadyClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardLocalSystemClock(KThread thread, SystemClockContext clockContext, long posixTime)
+ {
+ StandardLocalSystemClock.SetUpdateCallbackInstance(LocalClockContextWriter);
+
+ SteadyClockTimePoint currentTimePoint = StandardLocalSystemClock.GetSteadyClockCore().GetCurrentTimePoint(thread);
+ if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
+ {
+ StandardLocalSystemClock.SetSystemClockContext(clockContext);
+ }
+ else
+ {
+ if (StandardLocalSystemClock.SetCurrentTime(thread, posixTime) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set current local time");
+ }
+ }
+
+ StandardLocalSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardNetworkSystemClock(SystemClockContext clockContext, TimeSpanType sufficientAccuracy)
+ {
+ StandardNetworkSystemClock.SetUpdateCallbackInstance(NetworkClockContextWriter);
+
+ if (StandardNetworkSystemClock.SetSystemClockContext(clockContext) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set network SystemClockContext");
+ }
+
+ StandardNetworkSystemClock.SetStandardNetworkClockSufficientAccuracy(sufficientAccuracy);
+ StandardNetworkSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupTimeZoneManager(string locationName, SteadyClockTimePoint timeZoneUpdatedTimePoint, uint totalLocationNameCount, UInt128 timeZoneRuleVersion, Stream timeZoneBinaryStream)
+ {
+ if (TimeZone.Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set DeviceLocationName with a given TimeZoneBinary");
+ }
+
+ TimeZone.Manager.SetUpdatedTime(timeZoneUpdatedTimePoint, true);
+ TimeZone.Manager.SetTotalLocationNameCount(totalLocationNameCount);
+ TimeZone.Manager.SetTimeZoneRuleVersion(timeZoneRuleVersion);
+ TimeZone.Manager.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupEphemeralNetworkSystemClock()
+ {
+ EphemeralNetworkSystemClock.SetUpdateCallbackInstance(EphemeralClockContextWriter);
+ EphemeralNetworkSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardUserSystemClock(KThread thread, bool isAutomaticCorrectionEnabled, SteadyClockTimePoint steadyClockTimePoint)
+ {
+ if (StandardUserSystemClock.SetAutomaticCorrectionEnabled(thread, isAutomaticCorrectionEnabled) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set automatic user time correction state");
+ }
+
+ StandardUserSystemClock.SetAutomaticCorrectionUpdatedTime(steadyClockTimePoint);
+ StandardUserSystemClock.MarkInitialized();
+
+ SharedMemory.SetAutomaticCorrectionEnabled(isAutomaticCorrectionEnabled);
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetStandardSteadyClockRtcOffset(KThread thread, TimeSpanType rtcOffset)
+ {
+ StandardSteadyClock.SetSetupValue(rtcOffset);
+
+ TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread);
+
+ SharedMemory.SetSteadyClockRawTimePoint(thread, currentTimePoint);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs
new file mode 100644
index 0000000000..f714c66230
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.Types;
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class TimeSharedMemory
+ {
+ private Switch _device;
+ private KSharedMemory _sharedMemory;
+ private long _timeSharedMemoryAddress;
+ private int _timeSharedMemorySize;
+
+ private const uint SteadyClockContextOffset = 0x00;
+ private const uint LocalSystemClockContextOffset = 0x38;
+ private const uint NetworkSystemClockContextOffset = 0x80;
+ private const uint AutomaticCorrectionEnabledOffset = 0xC8;
+
+ public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
+ {
+ _device = device;
+ _sharedMemory = sharedMemory;
+ _timeSharedMemoryAddress = timeSharedMemoryAddress;
+ _timeSharedMemorySize = timeSharedMemorySize;
+
+ // Clean the shared memory
+ _device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize);
+ }
+
+ public KSharedMemory GetSharedMemory()
+ {
+ return _sharedMemory;
+ }
+
+ public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType currentTimePoint)
+ {
+ TimeSpanType ticksTimeSpan;
+
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
+ {
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ }
+
+ SteadyClockContext context = new SteadyClockContext
+ {
+ InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds),
+ ClockSourceId = clockSourceId
+ };
+
+ WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
+ }
+
+ public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled)
+ {
+ // We convert the bool to byte here as a bool in C# takes 4 bytes...
+ WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled));
+ }
+
+ public void SetSteadyClockRawTimePoint(KThread thread, TimeSpanType currentTimePoint)
+ {
+ SteadyClockContext context = ReadObjectFromSharedMemory(SteadyClockContextOffset, 4);
+ TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+
+ context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds);
+
+ WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
+ }
+
+ public void UpdateLocalSystemClockContext(SystemClockContext context)
+ {
+ WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context);
+ }
+
+ public void UpdateNetworkSystemClockContext(SystemClockContext context)
+ {
+ WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
+ }
+
+ private T ReadObjectFromSharedMemory(long offset, long padding)
+ {
+ long indexOffset = _timeSharedMemoryAddress + offset;
+
+ T result;
+ uint index;
+ uint possiblyNewIndex;
+
+ do
+ {
+ index = _device.Memory.ReadUInt32(indexOffset);
+
+ long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf();
+
+ result = _device.Memory.ReadStruct(objectOffset);
+
+ Thread.MemoryBarrier();
+
+ possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset);
+ } while (index != possiblyNewIndex);
+
+ return result;
+ }
+
+ private void WriteObjectToSharedMemory(long offset, long padding, T value)
+ {
+ long indexOffset = _timeSharedMemoryAddress + offset;
+ uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1;
+ long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf();
+
+ _device.Memory.WriteStruct(objectOffset, value);
+
+ Thread.MemoryBarrier();
+
+ _device.Memory.WriteUInt32(indexOffset, newIndex);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
index 3a98013e11..b32a979578 100644
--- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
@@ -903,7 +903,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ParsePosixName(name.ToCharArray(), out outRules, false);
}
- internal static unsafe bool LoadTimeZoneRules(out TimeZoneRule outRules, Stream inputData)
+ internal static unsafe bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData)
{
outRules = new TimeZoneRule
{
@@ -1357,10 +1357,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int[] ip = MonthsLengths[IsLeap((int)year)];
- while (dayOfYear >= ip[calendarTime.Month])
+ for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month)
{
- calendarTime.Month += 1;
-
dayOfYear -= ip[calendarTime.Month];
}
@@ -1709,7 +1707,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Time = new CalendarTime()
{
Year = (short)calendarTime.Year,
- Month = calendarTime.Month,
+ Month = (sbyte)(calendarTime.Month + 1),
Day = calendarTime.Day,
Hour = calendarTime.Hour,
Minute = calendarTime.Minute,
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
new file mode 100644
index 0000000000..8a2a55b9b1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
@@ -0,0 +1,197 @@
+using LibHac.Fs;
+using LibHac.Fs.NcaUtils;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Resource;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
+
+namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
+{
+ class TimeZoneContentManager
+ {
+ private const long TimeZoneBinaryTitleId = 0x010000000000080E;
+
+ private Switch _device;
+ private string[] _locationNameCache;
+
+ public TimeZoneManager Manager { get; private set; }
+
+ public TimeZoneContentManager()
+ {
+ Manager = new TimeZoneManager();
+ }
+
+ internal void Initialize(TimeManager timeManager, Switch device)
+ {
+ _device = device;
+
+ InitializeLocationNameCache();
+
+ SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(null);
+
+ ResultCode result = GetTimeZoneBinary("UTC", out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
+
+ if (result == ResultCode.Success)
+ {
+ // TODO: Read TimeZoneVersion from sysarchive.
+ timeManager.SetupTimeZoneManager("UTC", timeZoneUpdatedTimePoint, (uint)_locationNameCache.Length, new UInt128(), timeZoneBinaryStream);
+
+ ncaFile.Dispose();
+ }
+ else
+ {
+ // In the case the user don't have the timezone system archive, we just mark the manager as initialized.
+ Manager.MarkInitialized();
+ }
+ }
+
+ private void InitializeLocationNameCache()
+ {
+ if (HasTimeZoneBinaryTitle())
+ {
+ using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ {
+ Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
+ Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
+
+ StreamReader reader = new StreamReader(binaryListStream);
+
+ List locationNameList = new List();
+
+ string locationName;
+ while ((locationName = reader.ReadLine()) != null)
+ {
+ locationNameList.Add(locationName);
+ }
+
+ _locationNameCache = locationNameList.ToArray();
+ }
+ }
+ else
+ {
+ _locationNameCache = new string[0];
+
+ Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this warning. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
+ }
+ }
+
+ private bool IsLocationNameValid(string locationName)
+ {
+ foreach (string cachedLocationName in _locationNameCache)
+ {
+ if (cachedLocationName.Equals(locationName))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public ResultCode SetDeviceLocationName(string locationName)
+ {
+ ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
+
+ if (result == ResultCode.Success)
+ {
+ result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
+
+ ncaFile.Dispose();
+ }
+
+ return result;
+ }
+
+ public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
+ {
+ List locationNameList = new List();
+
+ for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
+ {
+ if (i < index)
+ {
+ continue;
+ }
+
+ string locationName = _locationNameCache[i];
+
+ // If the location name is too long, error out.
+ if (locationName.Length > 0x24)
+ {
+ outLocationNameArray = new string[0];
+
+ return ResultCode.LocationNameTooLong;
+ }
+
+ locationNameList.Add(locationName);
+ }
+
+ outLocationNameArray = locationNameList.ToArray();
+
+ return ResultCode.Success;
+ }
+
+ public string GetTimeZoneBinaryTitleContentPath()
+ {
+ return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
+ }
+
+ public bool HasTimeZoneBinaryTitle()
+ {
+ return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
+ }
+
+ internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile)
+ {
+ timeZoneBinaryStream = null;
+ ncaFile = null;
+
+ if (!IsLocationNameValid(locationName))
+ {
+ return ResultCode.TimeZoneNotFound;
+ }
+
+ ncaFile = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open);
+
+ Nca nca = new Nca(_device.System.KeySet, ncaFile);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
+
+ timeZoneBinaryStream = romfs.OpenFile($"/zoneinfo/{locationName}", OpenMode.Read).AsStream();
+
+ return ResultCode.Success;
+ }
+
+ internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
+ {
+ outRules = new TimeZoneRule
+ {
+ Ats = new long[TzMaxTimes],
+ Types = new byte[TzMaxTimes],
+ Ttis = new TimeTypeInfo[TzMaxTypes],
+ Chars = new char[TzCharsArraySize]
+ };
+
+ if (!HasTimeZoneBinaryTitle())
+ {
+ throw new InvalidSystemResourceException($"TimeZoneBinary system title not found! Please provide it. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
+ }
+
+ ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
+
+ if (result == ResultCode.Success)
+ {
+ result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
+
+ ncaFile.Dispose();
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
index 2497f6a375..1a80365ad9 100644
--- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
@@ -1,50 +1,28 @@
-using LibHac.Fs;
-using LibHac.Fs.NcaUtils;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.FileSystem;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Utilities;
using System.IO;
-using TimeZoneConverter;
-using TimeZoneConverter.Posix;
-
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
- public sealed class TimeZoneManager
+ class TimeZoneManager
{
- private const long TimeZoneBinaryTitleId = 0x010000000000080E;
+ private bool _isInitialized;
+ private TimeZoneRule _myRules;
+ private string _deviceLocationName;
+ private UInt128 _timeZoneRuleVersion;
+ private uint _totalLocationNameCount;
+ private SteadyClockTimePoint _timeZoneUpdateTimePoint;
+ private object _lock;
- private static TimeZoneManager instance;
-
- private static object instanceLock = new object();
-
- private Switch _device;
- private TimeZoneRule _myRules;
- private string _deviceLocationName;
- private string[] _locationNameCache;
-
- public static TimeZoneManager Instance
+ public TimeZoneManager()
{
- get
- {
- lock (instanceLock)
- {
- if (instance == null)
- {
- instance = new TimeZoneManager();
- }
+ _isInitialized = false;
+ _deviceLocationName = "UTC";
+ _timeZoneRuleVersion = new UInt128();
+ _lock = new object();
- return instance;
- }
- }
- }
-
- TimeZoneManager()
- {
- // Empty rules (UTC)
+ // Empty rules
_myRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
@@ -53,236 +31,237 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Chars = new char[TzCharsArraySize]
};
- _deviceLocationName = "UTC";
+ _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
}
- internal void Initialize(Switch device)
+ public bool IsInitialized()
{
- _device = device;
+ bool res;
- InitializeLocationNameCache();
- }
-
- private void InitializeLocationNameCache()
- {
- if (HasTimeZoneBinaryTitle())
+ lock (_lock)
{
- using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ res = _isInitialized;
+ }
+
+ return res;
+ }
+
+ public void MarkInitialized()
+ {
+ lock (_lock)
+ {
+ _isInitialized = true;
+ }
+ }
+
+ public ResultCode GetDeviceLocationName(out string deviceLocationName)
+ {
+ ResultCode result = ResultCode.UninitializedClock;
+
+ deviceLocationName = null;
+
+ lock (_lock)
+ {
+ if (_isInitialized)
{
- Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
- Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
-
- StreamReader reader = new StreamReader(binaryListStream);
-
- List locationNameList = new List();
-
- string locationName;
- while ((locationName = reader.ReadLine()) != null)
- {
- locationNameList.Add(locationName);
- }
-
- _locationNameCache = locationNameList.ToArray();
+ deviceLocationName = _deviceLocationName;
+ result = ResultCode.Success;
}
}
- else
- {
- ReadOnlyCollection timeZoneInfos = TimeZoneInfo.GetSystemTimeZones();
- _locationNameCache = new string[timeZoneInfos.Count];
- int i = 0;
-
- foreach (TimeZoneInfo timeZoneInfo in timeZoneInfos)
- {
- bool needConversion = TZConvert.TryWindowsToIana(timeZoneInfo.Id, out string convertedName);
- if (needConversion)
- {
- _locationNameCache[i] = convertedName;
- }
- else
- {
- _locationNameCache[i] = timeZoneInfo.Id;
- }
- i++;
- }
-
- // As we aren't using the system archive, "UTC" might not exist on the host system.
- // Load from C# TimeZone APIs UTC id.
- string utcId = TimeZoneInfo.Utc.Id;
- bool utcNeedConversion = TZConvert.TryWindowsToIana(utcId, out string utcConvertedName);
- if (utcNeedConversion)
- {
- utcId = utcConvertedName;
- }
-
- _deviceLocationName = utcId;
- }
+ return result;
}
- private bool IsLocationNameValid(string locationName)
+ public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream)
{
- foreach (string cachedLocationName in _locationNameCache)
+ ResultCode result = ResultCode.TimeZoneConversionFailed;
+
+ lock (_lock)
{
- if (cachedLocationName.Equals(locationName))
+ bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream);
+
+ if (timeZoneConversionSuccess)
{
- return true;
+ _deviceLocationName = locationName;
+ _myRules = rules;
+ result = ResultCode.Success;
}
}
- return false;
+
+ return result;
}
- public string GetDeviceLocationName()
+ public void SetTotalLocationNameCount(uint totalLocationNameCount)
{
- return _deviceLocationName;
- }
-
- public ResultCode SetDeviceLocationName(string locationName)
- {
- ResultCode resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName);
-
- if (resultCode == 0)
+ lock (_lock)
{
- _myRules = rules;
- _deviceLocationName = locationName;
+ _totalLocationNameCount = totalLocationNameCount;
}
-
- return resultCode;
}
- public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
+ public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount)
{
- List locationNameList = new List();
+ ResultCode result = ResultCode.UninitializedClock;
- for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
+ totalLocationNameCount = 0;
+
+ lock (_lock)
{
- if (i < index)
+ if (_isInitialized)
{
- continue;
- }
-
- string locationName = _locationNameCache[i];
-
- // If the location name is too long, error out.
- if (locationName.Length > 0x24)
- {
- outLocationNameArray = new string[0];
-
- return ResultCode.LocationNameTooLong;
- }
-
- locationNameList.Add(locationName);
- }
-
- outLocationNameArray = locationNameList.ToArray();
-
- return ResultCode.Success;
- }
-
- public uint GetTotalLocationNameCount()
- {
- return (uint)_locationNameCache.Length;
- }
-
- public string GetTimeZoneBinaryTitleContentPath()
- {
- return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
- }
-
- public bool HasTimeZoneBinaryTitle()
- {
- return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
- }
-
- internal ResultCode LoadTimeZoneRules(out TimeZoneRule outRules, string locationName)
- {
- outRules = new TimeZoneRule
- {
- Ats = new long[TzMaxTimes],
- Types = new byte[TzMaxTimes],
- Ttis = new TimeTypeInfo[TzMaxTypes],
- Chars = new char[TzCharsArraySize]
- };
-
- if (!IsLocationNameValid(locationName))
- {
- return ResultCode.TimeZoneNotFound;
- }
-
- if (!HasTimeZoneBinaryTitle())
- {
- // If the user doesn't have the system archives, we generate a POSIX rule string and parse it to generate a incomplete TimeZoneRule
- // TODO: As for now not having system archives is fine, we should enforce the usage of system archives later.
- Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system archive not found! Time conversions will not be accurate!");
- try
- {
- TimeZoneInfo info = TZConvert.GetTimeZoneInfo(locationName);
- string posixRule = PosixTimeZone.FromTimeZoneInfo(info);
-
- if (!TimeZone.ParsePosixName(posixRule, out outRules))
- {
- return ResultCode.TimeZoneConversionFailed;
- }
-
- return 0;
- }
- catch (TimeZoneNotFoundException)
- {
- Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {locationName})");
-
- return ResultCode.TimeZoneNotFound;
+ totalLocationNameCount = _totalLocationNameCount;
+ result = ResultCode.Success;
}
}
- else
+
+ return result;
+ }
+
+ public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false)
+ {
+ ResultCode result = ResultCode.UninitializedClock;
+
+ lock (_lock)
{
- using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ if (_isInitialized || bypassUninitialized)
{
- Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
- Stream tzIfStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
-
- if (!TimeZone.LoadTimeZoneRules(out outRules, tzIfStream))
- {
- return ResultCode.TimeZoneConversionFailed;
- }
+ _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint;
+ result = ResultCode.Success;
}
-
- return 0;
}
+
+ return result;
}
- internal ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
+ public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint)
{
- return ToCalendarTime(_myRules, time, out calendar);
- }
+ ResultCode result;
- internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
- {
- ResultCode error = TimeZone.ToCalendarTime(rules, time, out calendar);
-
- if (error != ResultCode.Success)
+ lock (_lock)
{
- return error;
+ if (_isInitialized)
+ {
+ timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint;
+ result = ResultCode.Success;
+ }
+ else
+ {
+ timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom();
+ result = ResultCode.UninitializedClock;
+ }
}
- return ResultCode.Success;
+ return result;
}
- internal ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
+ public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
{
- return ToPosixTime(_myRules, calendarTime, out posixTime);
- }
+ ResultCode result = ResultCode.Success;
- internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
- {
- ResultCode error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
-
- if (error != ResultCode.Success)
+ lock (_lock)
{
- return error;
+ bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);
+
+ if (!timeZoneConversionSuccess)
+ {
+ result = ResultCode.TimeZoneConversionFailed;
+ }
}
- return ResultCode.Success;
+ return result;
+ }
+
+ public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion)
+ {
+ lock (_lock)
+ {
+ _timeZoneRuleVersion = timeZoneRuleVersion;
+ }
+ }
+
+ public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion)
+ {
+ ResultCode result;
+
+ lock (_lock)
+ {
+ if (_isInitialized)
+ {
+ timeZoneRuleVersion = _timeZoneRuleVersion;
+ result = ResultCode.Success;
+ }
+ else
+ {
+ timeZoneRuleVersion = new UInt128();
+ result = ResultCode.UninitializedClock;
+ }
+ }
+
+ return result;
+ }
+
+ public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
+ {
+ ResultCode result;
+
+ lock (_lock)
+ {
+ if (_isInitialized)
+ {
+ result = ToCalendarTime(_myRules, time, out calendar);
+ }
+ else
+ {
+ calendar = new CalendarInfo();
+ result = ResultCode.UninitializedClock;
+ }
+ }
+
+ return result;
+ }
+
+ public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
+ {
+ ResultCode result;
+
+ lock (_lock)
+ {
+ result = TimeZone.ToCalendarTime(rules, time, out calendar);
+ }
+
+ return result;
+ }
+
+ public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
+ {
+ ResultCode result;
+
+ lock (_lock)
+ {
+ if (_isInitialized)
+ {
+ result = ToPosixTime(_myRules, calendarTime, out posixTime);
+ }
+ else
+ {
+ posixTime = 0;
+ result = ResultCode.UninitializedClock;
+ }
+ }
+
+ return result;
+ }
+
+ public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
+ {
+ ResultCode result;
+
+ lock (_lock)
+ {
+ result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
+ }
+
+ return result;
}
}
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs
new file mode 100644
index 0000000000..4cf1fc9918
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs
@@ -0,0 +1,12 @@
+using Ryujinx.HLE.Utilities;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct SteadyClockContext
+ {
+ public ulong InternalOffset;
+ public UInt128 ClockSourceId;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
index 823c828888..3fcd3a1444 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
@@ -8,10 +8,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
LocalSystemClockWritableMask = 0x1,
UserSystemClockWritableMask = 0x2,
NetworkSystemClockWritableMask = 0x4,
- UnknownPermissionMask = 0x8,
+ TimeZoneWritableMask = 0x8,
+ SteadyClockWritableMask = 0x10,
+ BypassUninitialized = 0x20,
- User = 0,
- Applet = LocalSystemClockWritableMask | UserSystemClockWritableMask | UnknownPermissionMask,
- System = NetworkSystemClockWritableMask
+ User = 0,
+ Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask,
+ System = NetworkSystemClockWritableMask,
+ SystemUpdate = BypassUninitialized,
+ Repair = SteadyClockWritableMask,
+ Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask
}
}
diff --git a/Ryujinx/Ui/NpadController.cs b/Ryujinx/Ui/NpadController.cs
index 7b61af194f..c0baf9fb42 100644
--- a/Ryujinx/Ui/NpadController.cs
+++ b/Ryujinx/Ui/NpadController.cs
@@ -170,7 +170,7 @@ namespace Ryujinx.UI.Input
{
int axis = controllerInputId - ControllerInputId.Axis0;
- return Math.Abs(joystickState.GetAxis(axis)) > Deadzone;
+ return joystickState.GetAxis(axis) > TriggerThreshold;
}
else if (controllerInputId <= ControllerInputId.Hat2Right)
{