Fill in more gaps in ZuneDBApi

This commit is contained in:
Yoshi Askharoun
2026-07-27 01:28:59 -05:00
parent 484e9ab4c3
commit f6192ae3da
19 changed files with 424 additions and 16 deletions
+6
View File
@@ -0,0 +1,6 @@
public enum ELibraryFilter
{
eLibraryFilterAll = -1,
eLibraryFilterInLibraryOnly = 1,
eLibraryFilterNotInLibrary = 0
}
+7
View File
@@ -0,0 +1,7 @@
public enum EQuickMixState
{
eQuickMixStateNotFound = 2,
eQuickMixStateReady = 1,
eQuickMixStateUnknown = 0,
eQuickMixStateInvalid = -1
}
+7
View File
@@ -0,0 +1,7 @@
public enum EWatchType
{
eWatchCount = 3,
eWatching = 2,
eWatched = 1,
eUnWatched = 0
}
@@ -0,0 +1,9 @@
namespace Microsoft.Zune.Service;
public class PointsPayment : PaymentInstrument
{
public PointsPayment()
: base(null, PaymentType.Points)
{
}
}
+18
View File
@@ -0,0 +1,18 @@
namespace Microsoft.Zune.Util;
public class Point
{
public int Y { get; set; }
public int X { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public Point() : this(0, 0)
{
}
}
+18
View File
@@ -0,0 +1,18 @@
namespace Microsoft.Zune.Util;
public class Size
{
public int Height { get; set; }
public int Width { get; set; }
public Size(int width, int height)
{
Width = width;
Height = height;
}
public Size() : this(0, 0)
{
}
}
+56 -8
View File
@@ -1,5 +1,3 @@
using System;
using System.Drawing;
using Microsoft.Iris; using Microsoft.Iris;
namespace Microsoft.Zune.Util; namespace Microsoft.Zune.Util;
@@ -7,7 +5,9 @@ namespace Microsoft.Zune.Util;
// Original wraps native Windows 7 taskbar integration (thumbnail preview window, jump // Original wraps native Windows 7 taskbar integration (thumbnail preview window, jump
// list command dispatch via window messages). Not reverse engineered — see // list command dispatch via window messages). Not reverse engineered — see
// logs/Microsoft.Zune/Util/DownloadManager.md. // logs/Microsoft.Zune/Util/DownloadManager.md.
public class TaskbarPlayer : IDisposable // Original derives from Microsoft.Iris.ModelItem, which is where INotifyPropertyChanged
// (PropertyChanged event, FirePropertyChanged) comes from.
public class TaskbarPlayer : ModelItem, IDisposable
{ {
private static TaskbarPlayer sm_taskbarPlayer; private static TaskbarPlayer sm_taskbarPlayer;
@@ -17,19 +17,65 @@ public class TaskbarPlayer : IDisposable
public Point RestorePosition { get; private set; } public Point RestorePosition { get; private set; }
public WindowState RestoreState { get; set; } private WindowState m_restoreState = WindowState.Maximized;
public WindowState RestoreState
{
get => m_restoreState;
set
{
if (m_restoreState != value)
{
m_restoreState = value;
FirePropertyChanged(nameof(RestoreState));
}
}
}
public Size PopupSize { get; private set; } public Size PopupSize { get; private set; }
public Point PopupPosition { get; private set; } public Point PopupPosition { get; private set; }
public bool PopupVisible { get; set; } private bool m_fPopupVisible;
public bool ToolbarVisible { get; set; } public bool PopupVisible
{
get => m_fPopupVisible;
set
{
if (m_fPopupVisible == value) return;
m_fPopupVisible = value;
FirePropertyChanged(nameof(PopupVisible));
}
}
private bool m_fToolbarVisible;
public bool ToolbarVisible
{
get => m_fToolbarVisible;
set
{
if (m_fToolbarVisible == value) return;
m_fToolbarVisible = value;
FirePropertyChanged(nameof(ToolbarVisible));
}
}
public bool EnableToolbar { get; set; } public bool EnableToolbar { get; set; }
public bool ShowToolbar { get; set; } private bool m_fShowToolbar;
public bool ShowToolbar
{
get => m_fShowToolbar;
set
{
if (m_fShowToolbar == value) return;
m_fShowToolbar = value;
FirePropertyChanged(nameof(ShowToolbar));
}
}
public Command Restore { get; } = new(); public Command Restore { get; } = new();
@@ -45,9 +91,11 @@ public class TaskbarPlayer : IDisposable
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing)
base.Dispose();
} }
public void Dispose() public new void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
@@ -0,0 +1,25 @@
using Microsoft.Iris;
namespace MicrosoftZuneLibrary;
public class AsyncQueryParams
{
public Guid ArtistId { get; }
public bool GetFriends { get; }
public int UserId { get; }
public DeferredInvokeHandler SetResultsHandler { get; }
public bool ResolveZuneTags { get; }
public AsyncQueryParams(Guid artistId, bool getFriends, int userId, DeferredInvokeHandler setResultsHandler, bool resolveZuneTags)
{
ArtistId = artistId;
GetFriends = getFriends;
UserId = userId;
SetResultsHandler = setResultsHandler;
ResolveZuneTags = resolveZuneTags;
}
}
@@ -0,0 +1,8 @@
namespace MicrosoftZuneLibrary;
public class FriendOfArtist
{
public string ZuneTag { get; set; }
public int Id { get; set; }
}
@@ -0,0 +1,165 @@
using System.Collections;
using System.Threading;
using Microsoft.Iris;
namespace MicrosoftZuneLibrary;
public class FriendsOfArtistQuery : ModelItem, IDisposable
{
private Guid m_artistId;
private bool m_getFriends;
private int m_userId;
private bool m_resolveZuneTags;
private bool m_hasFriends;
private bool m_queryComplete;
private IList m_friends;
private IList m_friendsWithFavs;
private IList m_friendsWithMostPlayedSongs;
private IList m_friendsWithMostPlayedArtists;
private static FriendsOfArtistQuery m_singletonInstance;
public IList FriendsWithMostPlayedArtists => m_friendsWithMostPlayedArtists;
public IList FriendsWithMostPlayedSongs => m_friendsWithMostPlayedSongs;
public IList FriendsWithFavs => m_friendsWithFavs;
public IList Friends => m_friends;
public bool HasFriends => m_hasFriends;
public bool QueryComplete
{
get => m_queryComplete;
set
{
if (value != m_queryComplete)
{
m_queryComplete = value;
FirePropertyChanged(nameof(QueryComplete));
}
}
}
public static FriendsOfArtistQuery Instance => m_singletonInstance ??= new FriendsOfArtistQuery();
private FriendsOfArtistQuery()
{
}
public void Query(Guid artistId, bool getFriends, int userId) => Query(artistId, getFriends, userId, resolveZuneTags: true);
public void Query(Guid artistId, bool getFriends, int userId, bool resolveZuneTags)
{
try
{
Monitor.Enter(m_singletonInstance);
QueryComplete = false;
if (artistId == m_artistId && (!getFriends || m_getFriends) && userId == m_userId)
{
QueryComplete = true;
return;
}
m_artistId = artistId;
m_getFriends = getFriends;
m_userId = userId;
m_resolveZuneTags = resolveZuneTags;
SetResults(hasFriends: false, null, null, null, null);
if (artistId != Guid.Empty)
{
AsyncQueryParams state = new(artistId, getFriends, userId, DeferredSetResults, m_resolveZuneTags);
ThreadPool.QueueUserWorkItem(AsyncQuery, state);
}
else
{
QueryComplete = true;
}
}
finally
{
Monitor.Exit(m_singletonInstance);
}
}
// The original resolves the artist's user cards (and, per card, the ZuneTag) via the native
// ZuneLibraryExports.UserCardsForMedia/GetFieldValues exports. Those resolve through the same
// opaque native singleton/factory table documented as unreachable via static analysis in
// logs/MicrosoftZuneInterop/IQueryPropertyBag.md (no recoverable RTTI/symbol for the concrete
// implementation, would need dynamic tracing) — see logs/MicrosoftZuneLibrary/FriendsOfArtistQuery.md.
// Until that native database exists, every query completes with "no friends found," matching
// the same treatment as MicrosoftZuneLibrary/LibraryDataProviderItemBase.GetFieldValue/SetFieldValue.
private static void AsyncQuery(object obj)
{
AsyncQueryParams asyncQueryParams = (AsyncQueryParams)obj;
SetResultsParams args = new(hasFriends: false, null, null, null, null);
Application.DeferredInvoke(asyncQueryParams.SetResultsHandler, args);
}
private void SetResults(bool hasFriends, IList friends, IList friendsWithFavs, IList friendsWithMostPlayedSongs, IList friendsWithMostPlayedArtists)
{
if (m_friends != friends)
{
m_friends = friends;
FirePropertyChanged(nameof(Friends));
}
if (m_friendsWithFavs != friendsWithFavs)
{
m_friendsWithFavs = friendsWithFavs;
FirePropertyChanged(nameof(FriendsWithFavs));
}
if (m_friendsWithMostPlayedSongs != friendsWithMostPlayedSongs)
{
m_friendsWithMostPlayedSongs = friendsWithMostPlayedSongs;
FirePropertyChanged(nameof(FriendsWithMostPlayedSongs));
}
if (m_friendsWithMostPlayedArtists != friendsWithMostPlayedArtists)
{
m_friendsWithMostPlayedArtists = friendsWithMostPlayedArtists;
FirePropertyChanged(nameof(FriendsWithMostPlayedArtists));
}
if (m_hasFriends != hasFriends)
{
m_hasFriends = hasFriends;
FirePropertyChanged(nameof(HasFriends));
}
}
private void DeferredSetResults(object obj)
{
QueryComplete = true;
SetResultsParams setResultsParams = (SetResultsParams)obj;
SetResults(setResultsParams.HasFriends, setResultsParams.Friends, setResultsParams.FriendsWithFavs, setResultsParams.FriendsWithMostPlayedSongs, setResultsParams.FriendsWithMostPlayedArtists);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
base.Dispose();
}
public new void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~FriendsOfArtistQuery()
{
Dispose(false);
}
}
@@ -0,0 +1,25 @@
using System.Collections;
namespace MicrosoftZuneLibrary;
public class SetResultsParams
{
public bool HasFriends { get; }
public IList Friends { get; }
public IList FriendsWithFavs { get; }
public IList FriendsWithMostPlayedSongs { get; }
public IList FriendsWithMostPlayedArtists { get; }
public SetResultsParams(bool hasFriends, IList friends, IList friendsWithFavs, IList friendsWithMostPlayedSongs, IList friendsWithMostPlayedArtists)
{
HasFriends = hasFriends;
Friends = friends;
FriendsWithFavs = friendsWithFavs;
FriendsWithMostPlayedSongs = friendsWithMostPlayedSongs;
FriendsWithMostPlayedArtists = friendsWithMostPlayedArtists;
}
}
@@ -551,8 +551,7 @@ namespace Microsoft.Zune.Shell
if (num > 0) if (num > 0)
{ {
Debug.WriteLine(str); throw new ZuneShellException("Internal Zune Shell error", $"Scripting errors encountered (Process ID) = {Environment.ProcessId.ToString(CultureInfo.InvariantCulture)}\n\n{str}");
throw new ZuneShellException("Internal Zune Shell error", $"Scripting errors encountered (Process ID) = {Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)}\n\n{str}");
} }
} }
-1
View File
@@ -4,7 +4,6 @@
// MVID: FC8028F3-A47B-4FB4-B35B-11D1752D8264 // MVID: FC8028F3-A47B-4FB4-B35B-11D1752D8264
// Assembly location: C:\Program Files\Zune\ZuneShell.dll // Assembly location: C:\Program Files\Zune\ZuneShell.dll
using Microsoft.Zune.Util;
using System.Drawing; using System.Drawing;
namespace ZuneUI namespace ZuneUI
-1
View File
@@ -4,7 +4,6 @@
// MVID: FC8028F3-A47B-4FB4-B35B-11D1752D8264 // MVID: FC8028F3-A47B-4FB4-B35B-11D1752D8264
// Assembly location: C:\Program Files\Zune\ZuneShell.dll // Assembly location: C:\Program Files\Zune\ZuneShell.dll
using Microsoft.Zune.Util;
using MicrosoftZuneLibrary; using MicrosoftZuneLibrary;
using System.Drawing; using System.Drawing;
+2
View File
@@ -21,6 +21,8 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using UIXControls; using UIXControls;
using ZuneDBApi.Abstractions; using ZuneDBApi.Abstractions;
using Point = Microsoft.Zune.Util.Point;
using Size = Microsoft.Zune.Util.Size;
namespace ZuneUI namespace ZuneUI
{ {
-1
View File
@@ -5,7 +5,6 @@
// Assembly location: C:\Program Files\Zune\ZuneShell.dll // Assembly location: C:\Program Files\Zune\ZuneShell.dll
using Microsoft.Zune.Service; using Microsoft.Zune.Service;
using Microsoft.Zune.Util;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
+1 -2
View File
@@ -5,11 +5,10 @@
// Assembly location: C:\Program Files\Zune\ZuneShell.dll // Assembly location: C:\Program Files\Zune\ZuneShell.dll
using Microsoft.Iris; using Microsoft.Iris;
using Microsoft.Zune.Util;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using Microsoft.Zune.Util;
namespace ZuneUI namespace ZuneUI
{ {
@@ -0,0 +1,75 @@
# MicrosoftZuneLibrary.FriendsOfArtistQuery — decompilation log
Append-only. Do not edit previous entries.
---
## 2026-07-26 — Stage 1 + 2 for FriendsOfArtistQuery, AsyncQueryParams, SetResultsParams
**Trigger:** explicit request to complete stages 1 and 2 for
`MicrosoftZuneLibrary.FriendsOfArtistQuery`. The user pointed out the type lives in
`ZuneDBApi.dll` (the original mixed-mode C++/CLI assembly at
`/home/yoshiask/repos/ZuneDev/windows/shared/zune-x64/Zune/ZuneDBApi.dll`), not in the
pure-managed `ZuneShell.dll` this session initially checked (which only carries the
`Microsoft.Zune.Shell`/`ZuneUI`/`ZuneXml` layer — the `MicrosoftZuneLibrary` namespace is
merged into `ZuneDBApi.dll`, confirmed via `ilspy_list_assembly_types` returning 0 hits
for that namespace in `ZuneShell.dll` and 160 hits in `ZuneDBApi.dll`).
**Source:** `mcp__ilspy__decompile_type` on `MicrosoftZuneLibrary.FriendsOfArtistQuery`,
`MicrosoftZuneLibrary.AsyncQueryParams`, and `MicrosoftZuneLibrary.SetResultsParams` (all
three are top-level types in that namespace, not nested — confirmed via
`list_assembly_types`).
**`AsyncQueryParams`/`SetResultsParams`**: plain immutable data-carrier classes with no
native dependency at all (just `Guid`/`bool`/`int`/`IList`/`Microsoft.Iris.DeferredInvokeHandler`
fields exposed as read-only properties). Transcribed as real logic — auto-properties set
from the constructor, matching CLAUDE.md's "prefer auto-properties" guidance rather than
the original's explicit backing-field get-only pattern (behaviorally identical).
**`FriendsOfArtistQuery`**: extends `Microsoft.Iris.ModelItem` (already present in this
repo via the `UIX.csproj` reference — real, not reimplemented this session) and is a
lazy, non-thread-safe singleton (`Instance` property), matching the original exactly
(no double-checked locking in the original either).
Real logic kept for:
- `Query(Guid, bool, int[, bool])`: the cache-hit short-circuit
(`artistId == m_artistId && (!getFriends || m_getFriends) && userId == m_userId`),
field assignment, `Guid.Empty` fast path, and `ThreadPool.QueueUserWorkItem` dispatch —
none of this touches native code.
- `SetResults`/`DeferredSetResults`: pure change-tracking + `FirePropertyChanged` calls
(`FirePropertyChanged` is `protected` on the real `ModelItem` base, so no reimplementation
needed).
- The `Application.DeferredInvoke(DeferredInvokeHandler, object)` completion hop: this is
real `Microsoft.Iris` framework machinery already implemented in `UIX.csproj`
(`libs/ZuneUIXTools/libs/MicrosoftIris/UIX/Microsoft/Iris/Application.cs:324`), not a
native call — confirmed by reading it before assuming it needed stubbing.
Stubbed (native-dependent):
- `AsyncQuery`'s body originally calls `global::<Module>.ZuneLibraryExports.UserCardsForMedia`
and `.GetFieldValues` — native module-level exports of `ZuneDBApi.dll`, resolved through
the same opaque native singleton/factory table documented as a dead end (no recoverable
RTTI or symbol for the concrete implementation, would require dynamic tracing of a running
process) in `logs/MicrosoftZuneInterop/IQueryPropertyBag.md`'s 2026-07-21 entry for
`ZuneLibraryExports.CreatePropertyBag`/`CreateMultiSortAttributes`. Per CLAUDE.md's
*Dealing with unknowns* step 4, this was not re-attempted from scratch — same native
export family, same already-documented dead end. ILSpy's decompilation of `AsyncQuery`
also emits unreadable `$ArrayType$...` struct spam for the native `DBPropertyRequestStruct`
stack-allocated array, consistent with CLAUDE.md's warning that native/mixed-mode
decompiler output is low quality and shouldn't be transcribed verbatim.
- Kept the surrounding framework intact (still runs on the thread pool, still completes via
`Application.DeferredInvoke` so `QueryComplete` flips to `true` and `HasFriends`/`Friends`
etc. fire their property-changed notifications) — every query now completes with "no
friends found," the same treatment as `MicrosoftZuneLibrary.LibraryDataProviderItemBase`'s
`GetFieldValue`/`SetFieldValue` stubs and `Microsoft.Zune.Util.DRMCanDoQuery`'s "every
query answers no" convention.
**Dispose/finalizer note:** ILSpy's decompilation of the original showed invalid C# —
`private void ~FriendsOfArtistQuery() {}` as an ordinary method with an empty body, and
`Dispose(bool P_0)` calling `Finalize()` directly in the non-disposing branch. Per
CLAUDE.md's *COM objects* rule (rewrite invalid C++-style destructor syntax as a proper
C# `~Type()` finalizer), this was rewritten as the standard `Dispose(bool)`/`~Type()`
pattern with identical externally-observable behavior (disposing calls `base.Dispose()`;
the finalizer path is a no-op, matching the original's empty destructor body) rather than
transcribing the decompiler artifact verbatim.
**Result:** `ZuneDBApi.csproj` builds with 0 errors (1351 pre-existing warnings, none new).