Merge pull request 'feat(asset-bridge): the 73 bodies action 0 could not see, and the ceiling that makes looking safe (Phase 6)' (#33) from feat/asset-bridge-p6 into edge
Reviewed-on: #33
This commit is contained in:
@@ -406,11 +406,123 @@ namespace Server.Custom.Bridge
|
||||
return false;
|
||||
}
|
||||
|
||||
int actions = ActionsOf(translated, fileType);
|
||||
|
||||
if (action >= actions)
|
||||
{
|
||||
// §4.10, measured in phase 6: this is the never-sweep rule again, one axis over.
|
||||
// A body's slots are contiguous and the next body's begin immediately after them,
|
||||
// so `index + action * 5` past the ceiling addresses ANOTHER BODY'S action — a
|
||||
// real record, at a real offset, that every check below passes. Measured on this
|
||||
// client: of 795 legacy bodies, 643 return a fully validated picture one action
|
||||
// past their band and **452 of those are byte-identical to body+1's action 0**.
|
||||
// Body 1 action 22 is an ettin; body 3 action 22 is an imp. Nothing downstream
|
||||
// can tell, which is why the refusal has to be here.
|
||||
reason = "body " + body + " has " + actions + " actions in file type " + fileType
|
||||
+ "; action " + action + " belongs to the next body";
|
||||
return false;
|
||||
}
|
||||
|
||||
index = AnimIndexOf(translated, fileType) + (action * 5) + direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many actions the index reserves for a body — the only safe ceiling, and it is
|
||||
/// the banding rather than the library's own answer.
|
||||
///
|
||||
/// <c>Animations.GetAnimLength</c> exists and looks like the right source. It is not:
|
||||
/// for a body reaching file type 5 as id 34 it answers **22** while
|
||||
/// <see cref="AnimIndexOf"/> puts that body in the 65-slot band, which is **13**. The
|
||||
/// two disagree on exactly one body of this client (reached by translation from body
|
||||
/// 276), and taking the larger number is nine actions of somebody else's art. So the
|
||||
/// count is derived from the same arithmetic that produces the offset, in the same
|
||||
/// file, where the two cannot drift apart.
|
||||
/// </summary>
|
||||
public static bool ActionCount(int body, out int actions, out int fileType, out string reason)
|
||||
{
|
||||
reason = null;
|
||||
actions = 0;
|
||||
fileType = 0;
|
||||
|
||||
if (body <= 0)
|
||||
{
|
||||
reason = "body " + body + " is not addressable";
|
||||
return false;
|
||||
}
|
||||
|
||||
int translated = body;
|
||||
int hue = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Animations.Translate(ref translated, ref hue);
|
||||
fileType = BodyConverter.Convert(ref translated);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
reason = "body.def/bodyconv.def lookup failed: " + e.GetType().Name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AnimDataPath(fileType) == null)
|
||||
{
|
||||
reason = "bodyconv sends body " + body + " to file type " + fileType
|
||||
+ ", which this client does not have";
|
||||
return false;
|
||||
}
|
||||
|
||||
actions = ActionsOf(translated, fileType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The banding of <see cref="AnimIndexOf"/>, read as an action count: a body's slots
|
||||
/// are five directions per action, so the band size divided by five is how many
|
||||
/// actions it owns.
|
||||
/// </summary>
|
||||
private static int ActionsOf(int body, int fileType)
|
||||
{
|
||||
return SlotsOf(body, fileType) / 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many index slots <see cref="AnimIndexOf"/>'s arithmetic gives this body. The
|
||||
/// bands are transcribed there and their sizes here, from the same source and in the
|
||||
/// same order, because a ceiling that disagrees with an offset is worse than no
|
||||
/// ceiling at all.
|
||||
/// </summary>
|
||||
private static int SlotsOf(int body, int fileType)
|
||||
{
|
||||
switch (fileType)
|
||||
{
|
||||
case 2:
|
||||
return body < 200 ? 110 : 65;
|
||||
|
||||
case 3:
|
||||
if (body < 300)
|
||||
return 65;
|
||||
|
||||
return body < 400 ? 110 : 175;
|
||||
|
||||
case 5:
|
||||
// Body 34's exclusion again — it is in the second band here, so it owns 13
|
||||
// actions and not 22. This is the one body `GetAnimLength` is wrong about.
|
||||
if (body < 200 && body != 34)
|
||||
return 110;
|
||||
|
||||
return body < 400 ? 65 : 175;
|
||||
|
||||
default: // 1 and 4 share their banding
|
||||
if (body < 200)
|
||||
return 110;
|
||||
|
||||
return body < 400 ? 65 : 175;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Animations.GetFileIndex</c>'s own arithmetic, which is private. The banding is
|
||||
/// per file type and the boundaries differ between them, so this is transcribed rather
|
||||
|
||||
@@ -85,8 +85,13 @@ namespace Server.Custom.Bridge
|
||||
/// stock client is 235 new sprites and two of them player-character bodies; and the
|
||||
/// player-body set no longer carries ghost ids. Every client file is byte-identical
|
||||
/// and the answer is different, which is precisely what this number exists to say.
|
||||
///
|
||||
/// **3** — phase 6 (§4.10, §11.2). A body with no art at action 0 is catalogued at
|
||||
/// the first action that has any, and its key names that action. 74 more bodies on a
|
||||
/// stock client, no existing key's bytes changed — but a body that was absent is now
|
||||
/// a row, which is the same "unchanged input, different answer" this number covers.
|
||||
/// </summary>
|
||||
public const int EXTRACTOR_VERSION = 2;
|
||||
public const int EXTRACTOR_VERSION = 3;
|
||||
|
||||
// ── the one slot (§3.2) ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -14,11 +14,20 @@ namespace Server.Custom.Bridge
|
||||
///
|
||||
/// One thumbnail per creature body: the working set that makes a bestiary, a marketplace
|
||||
/// listing and a character sheet render. Everything deeper — every action, every frame —
|
||||
/// is the same addressing scheme at a deeper key, fetched on demand in a later phase; this
|
||||
/// is the set that is worth importing before anything asks for it, because on this
|
||||
/// machine's client it is **1,022 sprites at about a kilobyte each** — 787 out of the
|
||||
/// legacy `anim*.mul` files and, since phase 4, 235 more out of `AnimationFrame*.uop`,
|
||||
/// which ServUO's vendored decoder never opens (§4.3, §4.9).
|
||||
/// is the same addressing scheme at a deeper key and is **not served** (§11.2, phase 6:
|
||||
/// the site shows still pictures, so frames wait for a consumer that wants them). This is
|
||||
/// the set that is worth importing before anything asks for it, because on this machine's
|
||||
/// client it is **1,096 sprites at about a kilobyte each** — 787 out of the legacy
|
||||
/// `anim*.mul` files, 235 more out of `AnimationFrame*.uop` since phase 4 (§4.3, §4.9),
|
||||
/// and 74 more since phase 6, which have no art at action 0 and real art deeper.
|
||||
///
|
||||
/// ── **One picture per body, at the first action that has one** ──
|
||||
///
|
||||
/// A key carries the action it came from — `body/820/a23` for a horse whose action 0 is
|
||||
/// empty — so the catalogue is still exactly one row per body, and the row says which
|
||||
/// picture it is. What it never does is decode an action the walk did not choose: a fetch
|
||||
/// for `body/820/a0` is `unsupported`, not a second attempt, because the slots past a
|
||||
/// body's band belong to the next body and every check passes on them (§4.10).
|
||||
///
|
||||
/// Two request kinds, which are §6's two stages for assets rather than for sources:
|
||||
///
|
||||
@@ -89,8 +98,22 @@ namespace Server.Custom.Bridge
|
||||
/// <summary>Bodies are addressable to 2047; the sweep behind §4.8 covered exactly this.</summary>
|
||||
private const int MaxBody = 2047;
|
||||
|
||||
/// <summary>The catalogue is first frames only. Deep keys are phase 6.</summary>
|
||||
private const int CatalogAction = 0;
|
||||
/// <summary>
|
||||
/// The action a thumbnail comes from when the body has one, which is nearly always.
|
||||
/// Everything deeper than a first frame is deferred — see §11.2.
|
||||
/// </summary>
|
||||
private const int PreferredAction = 0;
|
||||
|
||||
/// <summary>
|
||||
/// How far the fallback looks for a body with no art at <see cref="PreferredAction"/>.
|
||||
///
|
||||
/// 35 because that is the largest band any file type gives a body, so an action beyond
|
||||
/// it is not something the client's own layout can name. The legacy arm is bounded
|
||||
/// tighter still and per body, by <see cref="BridgeAssetValidator.ActionCount"/> —
|
||||
/// this is only the scan's outer stop, and it is the UOP arm's real one, where an
|
||||
/// action is a named entry rather than an offset.
|
||||
/// </summary>
|
||||
private const int MaxAction = 35;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
@@ -110,6 +133,14 @@ namespace Server.Custom.Bridge
|
||||
{
|
||||
public string Key;
|
||||
public int Body;
|
||||
|
||||
/// <summary>
|
||||
/// Which action this body's thumbnail came from — <see cref="PreferredAction"/>
|
||||
/// for all but 74 bodies on this client, and on the wire because the key names it
|
||||
/// (§5, §11.2). A consumer that assumes `a0` would build a dead URL for a horse.
|
||||
/// </summary>
|
||||
public int Action;
|
||||
|
||||
public int Direction;
|
||||
public int FileType;
|
||||
public string Sha256;
|
||||
@@ -130,8 +161,15 @@ namespace Server.Custom.Bridge
|
||||
private sealed class Catalog
|
||||
{
|
||||
public string Id;
|
||||
public readonly Dictionary<string, Sprite> ByKey =
|
||||
new Dictionary<string, Sprite>(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Keyed by **body**, not by asset key, since phase 6: a body's key now carries
|
||||
/// the action its picture came from, so the key cannot be spelled until the body
|
||||
/// has been resolved. A fetch arrives holding a key and has to reach the same
|
||||
/// sprite, which it does by parsing the body out of it and comparing.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, Sprite> ByBody = new Dictionary<int, Sprite>();
|
||||
|
||||
public readonly List<Sprite> Order = new List<Sprite>();
|
||||
|
||||
/// <summary>The next body the scan has yet to look at.</summary>
|
||||
@@ -264,6 +302,7 @@ namespace Server.Custom.Bridge
|
||||
item.Append(",\"width\":").Append(sprite.Width.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"height\":").Append(sprite.Height.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"body\":").Append(sprite.Body.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"action\":").Append(sprite.Action.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"direction\":").Append(sprite.Direction.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"source\":\"").Append(sprite.Source).Append('"');
|
||||
item.Append('}');
|
||||
@@ -413,9 +452,9 @@ namespace Server.Custom.Bridge
|
||||
/// </summary>
|
||||
private static string Render(Catalog catalog, Readers readers, string key)
|
||||
{
|
||||
int body;
|
||||
int body, action;
|
||||
|
||||
if (!TryParseKey(key, out body))
|
||||
if (!TryParseKey(key, out body, out action))
|
||||
{
|
||||
var bad = new StringBuilder(96);
|
||||
bad.Append("{\"key\":");
|
||||
@@ -437,12 +476,26 @@ namespace Server.Custom.Bridge
|
||||
return item.ToString();
|
||||
}
|
||||
|
||||
if (sprite.Action != action)
|
||||
{
|
||||
// The body has a picture, but not at the action this key names. Two ways to get
|
||||
// here and both are the caller's: an old manifest that catalogued this body at
|
||||
// `a0` before the client was patched, or a key someone built by assuming the
|
||||
// action. Neither is served — decoding the asked-for action instead would be
|
||||
// §4.10's wrong picture, arrived at politely.
|
||||
item.Append(",\"status\":\"unsupported\"");
|
||||
item.Append(",\"action\":").Append(sprite.Action.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append('}');
|
||||
return item.ToString();
|
||||
}
|
||||
|
||||
item.Append(",\"status\":\"ok\"");
|
||||
item.Append(",\"sha256\":\"").Append(sprite.Sha256).Append('"');
|
||||
item.Append(",\"bytes\":").Append(sprite.Png.Length.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"width\":").Append(sprite.Width.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"height\":").Append(sprite.Height.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"body\":").Append(sprite.Body.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"action\":").Append(sprite.Action.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"direction\":").Append(sprite.Direction.ToString(CultureInfo.InvariantCulture));
|
||||
item.Append(",\"source\":\"").Append(sprite.Source).Append('"');
|
||||
item.Append(",\"png\":\"").Append(Convert.ToBase64String(sprite.Png)).Append("\"}");
|
||||
@@ -459,13 +512,11 @@ namespace Server.Custom.Bridge
|
||||
/// </summary>
|
||||
private static Sprite Resolve(Catalog catalog, Readers readers, int body)
|
||||
{
|
||||
string key = Key(body);
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
Sprite cached;
|
||||
|
||||
if (catalog.ByKey.TryGetValue(key, out cached))
|
||||
if (catalog.ByBody.TryGetValue(body, out cached))
|
||||
return cached;
|
||||
}
|
||||
|
||||
@@ -473,38 +524,88 @@ namespace Server.Custom.Bridge
|
||||
? BridgeConfig.AssetPlayerDirection
|
||||
: BridgeConfig.AssetCreatureDirection;
|
||||
|
||||
// Legacy first, always. The vendored decoder is what 787 of this client's bodies come
|
||||
// out of, it is what phase 3 measured, and the UOP packages hold a different and
|
||||
// mostly disjoint set (measured: of the 244 bodies they carry, 8 also have legacy
|
||||
// art). So this is a fallback rather than a choice, and no body changes reader while
|
||||
// a client sits still.
|
||||
Sprite sprite = ResolveLegacy(key, readers, body, direction)
|
||||
?? ResolveUop(key, readers, body, direction);
|
||||
Sprite sprite = ResolveAny(readers, body, direction);
|
||||
|
||||
if (sprite == null)
|
||||
return null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (!catalog.ByKey.ContainsKey(key))
|
||||
if (!catalog.ByBody.ContainsKey(body))
|
||||
{
|
||||
catalog.ByKey[key] = sprite;
|
||||
catalog.ByBody[body] = sprite;
|
||||
catalog.Order.Add(sprite);
|
||||
}
|
||||
|
||||
return catalog.ByKey[key];
|
||||
return catalog.ByBody[body];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One body's thumbnail: action 0 if it has one, otherwise the first action that does.
|
||||
///
|
||||
/// ── **Why there is a fallback at all** ──
|
||||
///
|
||||
/// Through phase 5 a body with no art at action 0 was simply absent, and on this
|
||||
/// client **74 bodies are in exactly that state while carrying real art deeper** — 66
|
||||
/// of them UOP, 8 legacy. Body 820's first drawn action is 23, and it is a horse.
|
||||
/// They rendered as text on the bestiary for want of looking one action further.
|
||||
///
|
||||
/// ── **Why the key says which action it is** ──
|
||||
///
|
||||
/// The fallback's picture is `body/820/a23`, not `body/820/a0`. Naming it `a0` would
|
||||
/// have been fewer changes downstream and a key that lies about its content, which is
|
||||
/// the failure this protocol keeps meeting from other directions (§4.5, §4.8, §11.1).
|
||||
///
|
||||
/// ── **Why the ceiling is not a detail** ──
|
||||
///
|
||||
/// Scanning actions is the one thing that can walk off the end of a body's slots, and
|
||||
/// the slots immediately after a body's are the **next body's**. Measured in phase 6:
|
||||
/// 643 of 795 legacy bodies return a fully validated, correctly-sized picture one
|
||||
/// action past their band, and 452 of those are byte-identical to body+1's action 0.
|
||||
/// <see cref="BridgeAssetValidator.ResolveAnimation"/> refuses past the ceiling, so
|
||||
/// this walk cannot produce one — see §4.10.
|
||||
/// </summary>
|
||||
private static Sprite ResolveAny(Readers readers, int body, int direction)
|
||||
{
|
||||
int actions, fileType;
|
||||
string reason;
|
||||
|
||||
// The legacy ceiling. A body the legacy path cannot place at all still gets the UOP
|
||||
// arm below, where an action is a named entry rather than an offset into a band.
|
||||
if (!BridgeAssetValidator.ActionCount(body, out actions, out fileType, out reason))
|
||||
actions = 0;
|
||||
|
||||
for (int action = PreferredAction; action < MaxAction; action++)
|
||||
{
|
||||
// Legacy first, always. The vendored decoder is what 787 of this client's bodies
|
||||
// come out of, it is what phase 3 measured, and the UOP packages hold a different
|
||||
// and mostly disjoint set (measured: of the 244 bodies they carry, 8 also have
|
||||
// legacy art). So this is a fallback rather than a choice, and no body changes
|
||||
// reader while a client sits still.
|
||||
Sprite sprite = action < actions
|
||||
? ResolveLegacy(Key(body, action), readers, body, action, direction)
|
||||
: null;
|
||||
|
||||
if (sprite == null)
|
||||
sprite = ResolveUop(Key(body, action), readers, body, action, direction);
|
||||
|
||||
if (sprite != null)
|
||||
return sprite;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ServUO's vendored <c>Animations</c> over <c>anim*.mul</c>, behind §4.5's validator.
|
||||
/// </summary>
|
||||
private static Sprite ResolveLegacy(string key, Readers readers, int body, int direction)
|
||||
private static Sprite ResolveLegacy(string key, Readers readers, int body, int action, int direction)
|
||||
{
|
||||
int fileType, at;
|
||||
string reason;
|
||||
|
||||
if (!BridgeAssetValidator.ResolveAnimation(body, CatalogAction, direction,
|
||||
if (!BridgeAssetValidator.ResolveAnimation(body, action, direction,
|
||||
out fileType, out at, out reason))
|
||||
return null;
|
||||
|
||||
@@ -534,12 +635,12 @@ namespace Server.Custom.Bridge
|
||||
|
||||
try
|
||||
{
|
||||
return Decode(key, body, direction, fileType);
|
||||
return Decode(key, body, action, direction, fileType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("[Bridge] catalogue: body {0}: {1}: {2}",
|
||||
body, e.GetType().Name, e.Message);
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} action {1}: {2}: {3}",
|
||||
body, action, e.GetType().Name, e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -558,9 +659,9 @@ namespace Server.Custom.Bridge
|
||||
/// hash of a name carrying the body id, and the payload repeats that id in its own
|
||||
/// header for <see cref="BridgeUop.Group.TryOpen"/> to check. A miss is a miss.
|
||||
/// </summary>
|
||||
private static Sprite ResolveUop(string key, Readers readers, int body, int direction)
|
||||
private static Sprite ResolveUop(string key, Readers readers, int body, int action, int direction)
|
||||
{
|
||||
ulong hash = BridgeUop.HashOf(body, CatalogAction);
|
||||
ulong hash = BridgeUop.HashOf(body, action);
|
||||
|
||||
byte[] payload = null;
|
||||
string reason = null;
|
||||
@@ -574,8 +675,8 @@ namespace Server.Custom.Bridge
|
||||
|
||||
if (!package.TryRead(hash, out payload, out reason))
|
||||
{
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} in {1}: {2}",
|
||||
body, BridgeUop.PackageName(n), reason);
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} action {1} in {2}: {3}",
|
||||
body, action, BridgeUop.PackageName(n), reason);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -589,7 +690,8 @@ namespace Server.Custom.Bridge
|
||||
|
||||
if (!BridgeUop.Group.TryOpen(payload, body, out group, out reason))
|
||||
{
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} uop: {1}", body, reason);
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} action {1} uop: {2}",
|
||||
body, action, reason);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -607,7 +709,8 @@ namespace Server.Custom.Bridge
|
||||
// exactly the same condition — so it is absent, silently. Anything else is a
|
||||
// record this reader refused, and that is worth a line.
|
||||
if (!empty)
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} uop: {1}", body, reason);
|
||||
Console.WriteLine("[Bridge] catalogue: body {0} action {1} uop: {2}",
|
||||
body, action, reason);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -621,6 +724,7 @@ namespace Server.Custom.Bridge
|
||||
{
|
||||
Key = key,
|
||||
Body = body,
|
||||
Action = action,
|
||||
Direction = direction,
|
||||
FileType = 0,
|
||||
Png = png,
|
||||
@@ -631,14 +735,14 @@ namespace Server.Custom.Bridge
|
||||
};
|
||||
}
|
||||
|
||||
private static Sprite Decode(string key, int body, int direction, int fileType)
|
||||
private static Sprite Decode(string key, int body, int action, int direction, int fileType)
|
||||
{
|
||||
int hue = 0;
|
||||
|
||||
// `preserveHue: false` — the catalogue is the creature's own art, and a body-level hue
|
||||
// from Body.def belongs to a specific mob rather than to the species. §5's key scheme
|
||||
// is where a hued variant is expressed (`static/3922/h33`), not here.
|
||||
Frame[] frames = Animations.GetAnimation(body, CatalogAction, direction, ref hue, false, true);
|
||||
Frame[] frames = Animations.GetAnimation(body, action, direction, ref hue, false, true);
|
||||
|
||||
if (frames == null || frames.Length == 0 || frames[0] == null)
|
||||
return null;
|
||||
@@ -657,6 +761,7 @@ namespace Server.Custom.Bridge
|
||||
{
|
||||
Key = key,
|
||||
Body = body,
|
||||
Action = action,
|
||||
Direction = direction,
|
||||
FileType = fileType,
|
||||
Png = png,
|
||||
@@ -758,20 +863,28 @@ namespace Server.Custom.Bridge
|
||||
|
||||
// ── keys, cursors and the source id ──────────────────────────────────────────────────
|
||||
|
||||
private static string Key(int body)
|
||||
private static string Key(int body, int action)
|
||||
{
|
||||
return "body/" + body.ToString(CultureInfo.InvariantCulture)
|
||||
+ "/a" + CatalogAction.ToString(CultureInfo.InvariantCulture);
|
||||
+ "/a" + action.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `body/<id>/a0`, and nothing else in this phase. A deeper key
|
||||
/// (`body/400/a2/f3`) is well-formed under §5 and simply not served yet, so it comes
|
||||
/// back `unsupported` rather than being silently read as its own first frame.
|
||||
/// `body/<id>/a<n>`, and nothing else in this phase. A deeper key
|
||||
/// (`body/400/a2/f3`) is well-formed under §5 and simply not served, so it comes back
|
||||
/// `unsupported` rather than being silently read as its own first frame.
|
||||
///
|
||||
/// The action is parsed rather than required to be zero — 74 of this client's bodies
|
||||
/// are catalogued at a different one (§11.2) — but a parsed action is not an accepted
|
||||
/// one. <see cref="Render"/> serves a key only when it is the key the catalogue itself
|
||||
/// chose for that body, which is what keeps §4.10's ceiling from being reachable
|
||||
/// through a request: nothing the website can ask makes this decode an action the
|
||||
/// catalogue did not already pick.
|
||||
/// </summary>
|
||||
private static bool TryParseKey(string key, out int body)
|
||||
private static bool TryParseKey(string key, out int body, out int action)
|
||||
{
|
||||
body = 0;
|
||||
action = -1;
|
||||
|
||||
if (key == null)
|
||||
return false;
|
||||
@@ -787,7 +900,14 @@ namespace Server.Custom.Bridge
|
||||
if (body < 1 || body > MaxBody)
|
||||
return false;
|
||||
|
||||
return parts[2] == "a" + CatalogAction.ToString(CultureInfo.InvariantCulture);
|
||||
if (parts[2].Length < 2 || parts[2][0] != 'a')
|
||||
return false;
|
||||
|
||||
if (!Int32.TryParse(parts[2].Substring(1), NumberStyles.None,
|
||||
CultureInfo.InvariantCulture, out action))
|
||||
return false;
|
||||
|
||||
return action >= 0 && action < MaxAction;
|
||||
}
|
||||
|
||||
private static int ParseBodyCursor(string cursor)
|
||||
|
||||
Reference in New Issue
Block a user