外观
适用范围
本文记录的是社区服务端项目接口,不是 Candy Rufus Games 官方原版客户端的公共 API。代码需要在对应源码分支的 SERVER 构建中使用;接口名称和行为可能随社区项目版本变化。
本文档以当前源码为准,整理服务端插件开发时常用的生命周期、加载规则、命令接口、事件接口与注册方式。所有服务端插件代码需要在 SERVER 构建中使用,常见命名空间包括:
csharp
using Game.Server;
using Game.Server.Event;
using Game.Server.PlayerEvent;
using Game.Server.Terminal;
using Engine;插件加载规则
插件基类为 Game.Server.ServerPlugin。
csharp
public abstract class ServerPlugin
{
public abstract int Version { get; }
public abstract string Name { get; }
public abstract void Initialize();
public abstract void Load();
public abstract void Save();
public virtual void Update(float dt) { }
}- 外部插件 DLL 放在服务端插件目录
Plugins下,由ServerManager.LoadPluginsDll()扫描。 - DLL 中所有继承
ServerPlugin且非抽象的类会被实例化。 - DLL 中所有继承
AbstractProcessCmd且非抽象的命令类会自动注册到CmdManager。 - 内置插件和外部插件都会执行
Initialize();世界加载后执行Load(),保存时执行Save(),服务端每帧执行Update(dt)。 - 事件订阅建议放在
Initialize(),避免Load()多次执行导致重复订阅。 - 插件配置建议存到
Storage.GetSystemPath("app:/Configs")或当前世界目录,避免写入程序目录失败。
最小插件示例
csharp
using Game.Server;
namespace MyPlugin;
public class HelloPlugin : ServerPlugin
{
public override int Version => 10000; // 1.0.0
public override string Name => "示例插件";
public override void Initialize()
{
}
public override void Load()
{
}
public override void Save()
{
}
public override void Update(float dt)
{
}
}事件注册约定
大多数事件都有对应的 XxxEventManager.AddObject(this) 和 RemoveObject(this):
csharp
public override void Initialize()
{
MessageEventManager.AddObject(this);
PlayerEnterGameEventManager.AddObject(this);
}返回值约定:
- 多数
bool返回值表示是否允许原行为继续执行。 - 返回
false通常表示拦截、取消或禁止该行为。 - 管理器一般会遍历所有处理器,只要任意处理器返回
false,最终结果就是false。 FirstLevel多数接口暂未完整实现排序,建议默认返回0。
消息与告示牌接口
IMessageEventHandle
命名空间:Game.Server.Event
该接口保持旧插件兼容,告示牌相关插件应继续实现此接口。
csharp
public interface IMessageEventHandle
{
byte FirstLevel { get; }
void ReceiveMessage(
string playerName,
NetNode netNode,
Client From,
string message,
byte messageType,
out bool External);
bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer);
}说明:
ReceiveMessage在服务端收到聊天消息时触发。External用于标记外部消息显示样式;旧插件可继续使用。EditSignMessage在玩家请求编辑告示牌时触发,返回false可阻止打开编辑界面。- 当前版本为了兼容旧插件,
IMessageEventHandle不再使用ref string message,也不再把告示牌接口改成void。
示例:
csharp
public class SignGuardPlugin : ServerPlugin, IMessageEventHandle
{
public override int Version => 10000;
public override string Name => "告示牌保护";
public byte FirstLevel => 0;
public override void Initialize()
{
MessageEventManager.AddObject(this);
}
public bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer)
{
return componentPlayer != null;
}
public void ReceiveMessage(string playerName, NetNode netNode, Client From, string message, byte messageType, out bool External)
{
External = false;
}
public override void Load() { }
public override void Save() { }
}IMutableMessageEventHandle
命名空间:Game.Server.Event
新插件如果需要修改玩家聊天内容,可以额外实现该扩展接口。它不会替代 IMessageEventHandle。
csharp
public interface IMutableMessageEventHandle
{
void ReceiveMessage(
string playerName,
NetNode netNode,
Client From,
ref string message,
byte messageType);
}示例:
csharp
public class WordReplacePlugin : ServerPlugin, IMessageEventHandle, IMutableMessageEventHandle
{
public override int Version => 10000;
public override string Name => "聊天替换";
public byte FirstLevel => 0;
public override void Initialize()
{
MessageEventManager.AddObject(this);
}
public void ReceiveMessage(string playerName, NetNode netNode, Client From, string message, byte messageType, out bool External)
{
External = false;
}
public void ReceiveMessage(string playerName, NetNode netNode, Client From, ref string message, byte messageType)
{
message = message.Replace("bad", "***");
}
public bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer) => true;
public override void Load() { }
public override void Save() { }
}玩家连接与验证接口
IBanEventHandle
命名空间:Game.Server
csharp
public interface IBanEventHandle
{
byte FirstLevel { get; }
bool IsBan(
string nickname,
string id,
string password,
string ip,
NetNode netNode,
Client client,
out bool UseExternalPassword);
bool IsBanIp(string ip, NetNode netNode, ConnectionRequest request);
}IsBan返回true表示拒绝玩家进入。UseExternalPassword可告知后续密码验证走外部逻辑。IsBanIp返回true表示按 IP 拒绝连接。
注册:
csharp
BanEventManager.AddObject(this);IPasswordValidationEventHandle
命名空间:Game.Server.Event
csharp
public interface IPasswordValidationEventHandle
{
byte FirstLevel { get; }
bool ValidatePassword(
string inputPassword,
string serverPassword,
bool useExternalPassword,
string nickname,
string communityAccountId,
string ipAddress,
NetNode netNode,
Client client,
out string errorMessage);
}- 用于自定义密码验证。
- 返回值表示验证是否通过。
errorMessage会作为失败提示。
注册:
csharp
PasswordValidationEventManager.AddObject(this);玩家生命周期接口
IPlayerEnterGameEventHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerEnterGameEventHandle
{
byte FirstLevel { get; }
void PlayerEnter(PlayerData playerData);
void PlayerLeave(PlayerData playerData);
}PlayerEnter在玩家进入服务器后触发。PlayerLeave在玩家离开服务器前触发。
注册:
csharp
PlayerEnterGameEventManager.AddObject(this);IPlayerIntoPlayingHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerIntoPlayingHandle
{
byte FirstLevel { get; }
void PlayerIntoPlayingEvent(ComponentPlayer componentPlayer);
}- 玩家实体进入正式 Playing 状态时触发。
注册:
csharp
PlayerIntoPlayingEventManager.AddObject(this);玩家行为接口
IPlayerBreakAndPlaceHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerBreakAndPlaceHandle
{
byte FirstLevel { get; }
bool PlayerPlaceEvent(ComponentPlayer componentPlayer, Point3 point, int placeBlockValue);
bool PlayerBreakEvent(ComponentPlayer componentPlayer, Point3 point, int digBlockValue, int toolLevel);
}- 返回
false可阻止放置或破坏方块。
注册:
csharp
PlayerBreakAndPlaceBlockEventManager.AddObject(this);IPlayerInteractEventHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerInteractEventHandle
{
byte FirstLevel { get; }
bool Interact(ComponentPlayer componentPlayer, CellFace cellFace);
bool Use(ComponentPlayer componentPlayer, object raycast, int activeBlockValue);
bool Hit(ComponentPlayer componentPlayer, ComponentBody componentBody, Vector3 hitPoint, Vector3 hitDirection);
bool Aim(ComponentPlayer componentPlayer, int activeBlockValue, Ray3 aim, AimState state);
}- 与方块交互、使用物品、击打、瞄准相关。
- 返回
false可拦截对应行为。
注册:
csharp
PlayerInteractEventManager.AddObject(this);IPlayerNetInteractEventHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerNetInteractEventHandle
{
byte FirstLevel { get; }
bool PlayerNetInteractEvent(
ComponentPlayer player,
ComponentPlayer.InteractEvent m_interactEvent,
Ray3 m_netInteractRay,
TerrainRaycastResult? m_netInteractRaycast);
}- 在接收到玩家网络交互包时触发,比部分本地交互接口更靠近网络层。
- 返回
false可阻止该网络交互。
注册:
csharp
PlayerNetInteractEventManager.AddObject(this);IPlayerMoveHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerMoveHandle
{
byte FirstLevel { get; }
bool PlayerMoveEvent(ComponentPlayer componentPlayer, Vector3 position);
}- 玩家移动时触发。
- 返回
false可阻止移动。
注册:
csharp
PlayerMoveEventManager.AddObject(this);IPlayerPositionSetHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerPositionSetHandle
{
byte FirstLevel { get; }
bool OnPlayerPositionSet(ComponentPlayer componentPlayer);
}- 玩家位置被强制设置时触发。
- 处理器会按
FirstLevel从小到大排序。 - 返回
false可阻止位置设置。
注册:
csharp
PlayerPositionSetEventManager.AddObject(this);背包与容器接口
IPlayerInventoryHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerInventoryHandle
{
byte FirstLevel { get; }
bool PlayerDrop(ComponentPlayer componentPlayer);
bool PlayerDrapDrop(ComponentPlayer componentPlayer, Vector3 worldPos, InventoryDragData inventoryDragData, int count);
bool PlayerHandleMoveItem(ComponentPlayer componentPlayer, IInventory sourceInventory, int sourceSlotIndex, IInventory targetInventory, int targetSlotIndex, int count);
bool PlayerHandleDragDrop(ComponentPlayer componentPlayer, IInventory sourceInventory, int sourceSlotIndex, DragMode dragMode, IInventory targetInventory, int targetSlotIndex, bool processingOnly);
}- 拦截玩家丢弃、拖拽丢弃、移动物品、拖拽物品。
- 返回
false可阻止对应背包操作。
注册:
csharp
PlayerInventoryEventManager.AddObject(this);IPlayerInventoryOpenHandle
命名空间:Game.Server.PlayerEvent
csharp
public interface IPlayerInventoryOpenHandle
{
byte FirstLevel { get; }
bool PlayerOpenInventoryEvent(PlayerData playerData, IInventory inventory);
bool PlayerOpenPointInventoryEvent(PlayerData playerData, Point3 point, IInventory inventory);
}- 打开容器界面时触发。
PlayerOpenPointInventoryEvent会额外提供容器位置。- 返回
false可阻止打开。
注册:
csharp
PlayerInventoryOpenEventManager.AddObject(this);方块、地形与环境接口
IBlockChangeEventHandle
命名空间:Game.Server.Event
csharp
public interface IBlockChangeEventHandle
{
byte FirstLevel { get; }
bool ChangeCell(int x, int y, int z, int oldValue, int newValue, ComponentMiner componentMiner);
void OnTerrainContentsGenerated(TerrainUpdater terrainUpdater, TerrainChunk chunk);
}ChangeCell在方块值变化时触发,返回false可阻止变化。OnTerrainContentsGenerated在地形内容生成后触发,可用于调整区块内容。
注册:
csharp
BlockChangeEventManager.AddObject(this);IExplodeEventHandle
命名空间:Game.Server.Event
csharp
public interface IExplodeEventHandle
{
byte FirstLevel { get; }
void Explode(int x, int y, int z, ref float pressure, bool isIncendiary, bool noExplosionSound, PlayerData miner);
}- 爆炸时触发。
- 可修改
pressure,设置为0通常可取消爆炸破坏效果。
注册:
csharp
ExplodeEventManager.AddObject(this);IFireEventHandle
命名空间:Game.Server.Event
csharp
public interface IFireEventHandle
{
byte FirstLevel { get; }
bool Fire(Ray3 ray, ComponentMiner componentMiner);
bool FireTerrain(CellFace cellFace, ComponentMiner componentMiner);
bool OnFireGeneration(int x, int y, int z, ref float spreadability);
}Fire:火柴向实体等目标点火时触发。FireTerrain:火柴向地形点火时触发。OnFireGeneration:火方块/火实体生成时触发,可修改spreadability。- 返回
false可阻止对应点火或火焰生成。
注册:
csharp
FireEventManager.AddObject(this);生物接口
ICreatureHealthEventHandle
命名空间:Game.Server.Event
csharp
public interface ICreatureHealthEventHandle
{
byte FirstLevel { get; }
bool Heal(ComponentHealth componentHealth, float amount);
bool Injure(ComponentHealth componentHealth, float amount, ComponentCreature attacker, string cause);
}- 生物治疗或受伤时触发。
- 返回
false可阻止治疗或伤害。
注册:
csharp
CreatureHealthEventManager.AddObject(this);ICreatureSpawnEventHandle
命名空间:Game.Server.Event
csharp
public interface ICreatureSpawnEventHandle
{
byte FirstLevel { get; }
bool Update(SubsystemCreatureSpawn subsystemCreatureSpawn, float dt);
void OnEntityAdded(SubsystemCreatureSpawn subsystemCreatureSpawn, Entity entity);
void OnEntityRemoved(SubsystemCreatureSpawn subsystemCreatureSpawn, Entity entity);
void OnPlayerSpawned(PlayerData playerData, Entity playerEntity, Vector3 position);
void InitCreatureTypes(SubsystemCreatureSpawn subsystemCreatureSpawn, List<SubsystemCreatureSpawn.CreatureType> creatureTypes);
}- 可参与生物刷新、实体添加移除、玩家出生、生物类型初始化。
Update返回false可阻止原刷新更新逻辑继续执行。
注册:
csharp
CreatureSpawnEventManager.AddObject(this);服务器命令接口
命令类继承 Game.Server.AbstractProcessCmd。外部 DLL 中非抽象命令类会自动注册。
csharp
public abstract class AbstractProcessCmd
{
public abstract string Cmd { get; }
public abstract string Introduce { get; }
public abstract int AuthLevel { get; }
public abstract DisplayType Display { get; }
public abstract void ProcessCmd();
public virtual IEnumerable<string> GetSuggestions(string[] args);
}DisplayType:
All:所有人都可以在帮助中看到。Authority:权限达到后可见。NoDisplay:不在帮助中显示。
命令示例:
csharp
public class CmdHello : AbstractProcessCmd
{
public override string Cmd => "hello";
public override string Introduce => "/hello - 测试命令";
public override int AuthLevel => 0;
public override DisplayType Display => DisplayType.All;
public override void ProcessCmd()
{
SendMessage("Hello", "插件命令执行成功");
}
}终端提示符与状态栏接口
这些接口用于增强服务端终端显示,只有增强终端模式支持;普通终端下会使用 NullPromptManager / NullStatusManager 降级。
IPromptProvider
命名空间:Game.Server.Terminal
csharp
public interface IPromptProvider
{
int Priority { get; }
IEnumerable<ConsolePart> GetPromptParts(TerminalContextForPart ctx);
float RefreshInterval => 10f;
}注册:
csharp
TerminalManager.RegisterPrompt(new MyPromptProvider());IStatusProvider
命名空间:Game.Server.Terminal
csharp
public interface IStatusProvider
{
int Priority { get; }
IEnumerable<ConsolePart> GetStatusParts(TerminalContextForPart ctx);
float RefreshInterval => 10f;
bool IsFullLine => false;
}注册:
csharp
TerminalManager.RegisterStatus(new MyStatusProvider());插件工程建议
外部插件项目通常引用:
xml
<ProjectReference Include="..\..\EngineServer\EngineServer.csproj" />
<ProjectReference Include="..\..\EntitySystemServer\EntitySystemServer.csproj" />
<ProjectReference Include="..\..\SurvivalcraftServer\SurvivalcraftServer.csproj" />建议:
- 插件 DLL 放到服务端
Plugins目录。 - 插件类和命令类都必须是
public且非抽象类。 - 订阅事件放在
Initialize()。 - 配置读取放在
Load(),保存放在Save()。 Update(dt)中避免阻塞 IO、网络请求和长耗时计算。- 需要世界路径时,可参考箱子锁插件按当前世界目录保存配置。
- 不要在日志中打印敏感 token、密码、玩家隐私信息。
当前兼容性说明
IMessageEventHandle已恢复旧签名,旧告示牌/聊天插件优先兼容。- 新的聊天内容修改能力通过
IMutableMessageEventHandle扩展实现。 - 若插件提示找不到接口,优先检查引用的服务端程序集版本,以及是否添加正确命名空间:
csharp
using Game.Server.Event;
using Game.Server.PlayerEvent;LS0tCnRpdGxlOiDnpL7ljLrmnI3liqHnq6/mj5Lku7bmjqXlj6MKZGF0ZTogMjAyNi0wNi0yMCAwMDowMDowMApkZXNjcmlwdGlvbjog6Z2i5ZCR56S+5Yy6IFN1cnZpdmFsY3JhZnQg5pyN5Yqh56uv5rqQ56CB55qE5o+S5Lu255Sf5ZG95ZGo5pyf44CB5ZG95Luk5LiO5LqL5Lu25o6l5Y+j5Y+C6ICDCi0tLQoKOjo6IHdhcm5pbmcg6YCC55So6IyD5Zu0CuacrOaWh+iusOW9leeahOaYr+ekvuWMuuacjeWKoeerr+mhueebruaOpeWPo++8jOS4jeaYryBDYW5keSBSdWZ1cyBHYW1lcyDlrpjmlrnljp/niYjlrqLmiLfnq6/nmoTlhazlhbEgQVBJ44CC5Luj56CB6ZyA6KaB5Zyo5a+55bqU5rqQ56CB5YiG5pSv55qEIGBTRVJWRVJgIOaehOW7uuS4reS9v+eUqO+8m+aOpeWPo+WQjeensOWSjOihjOS4uuWPr+iDvemaj+ekvuWMuumhueebrueJiOacrOWPmOWMluOAggo6OjoKCuacrOaWh+aho+S7peW9k+WJjea6kOeggeS4uuWHhu+8jOaVtOeQhuacjeWKoeerr+aPkuS7tuW8gOWPkeaXtuW4uOeUqOeahOeUn+WRveWRqOacn+OAgeWKoOi9veinhOWImeOAgeWRveS7pOaOpeWPo+OAgeS6i+S7tuaOpeWPo+S4juazqOWGjOaWueW8j+OAguaJgOacieacjeWKoeerr+aPkuS7tuS7o+eggemcgOimgeWcqCBgU0VSVkVSYCDmnoTlu7rkuK3kvb/nlKjvvIzluLjop4Hlkb3lkI3nqbrpl7TljIXmi6zvvJoKCmBgYGNzaGFycAp1c2luZyBHYW1lLlNlcnZlcjsKdXNpbmcgR2FtZS5TZXJ2ZXIuRXZlbnQ7CnVzaW5nIEdhbWUuU2VydmVyLlBsYXllckV2ZW50Owp1c2luZyBHYW1lLlNlcnZlci5UZXJtaW5hbDsKdXNpbmcgRW5naW5lOwpgYGAKCiMjIOaPkuS7tuWKoOi9veinhOWImQoK5o+S5Lu25Z+657G75Li6IGBHYW1lLlNlcnZlci5TZXJ2ZXJQbHVnaW5g44CCCgpgYGBjc2hhcnAKcHVibGljIGFic3RyYWN0IGNsYXNzIFNlcnZlclBsdWdpbgp7CiAgICBwdWJsaWMgYWJzdHJhY3QgaW50IFZlcnNpb24geyBnZXQ7IH0KICAgIHB1YmxpYyBhYnN0cmFjdCBzdHJpbmcgTmFtZSB7IGdldDsgfQogICAgcHVibGljIGFic3RyYWN0IHZvaWQgSW5pdGlhbGl6ZSgpOwogICAgcHVibGljIGFic3RyYWN0IHZvaWQgTG9hZCgpOwogICAgcHVibGljIGFic3RyYWN0IHZvaWQgU2F2ZSgpOwogICAgcHVibGljIHZpcnR1YWwgdm9pZCBVcGRhdGUoZmxvYXQgZHQpIHsgfQp9CmBgYAoKLSDlpJbpg6jmj5Lku7YgRExMIOaUvuWcqOacjeWKoeerr+aPkuS7tuebruW9lSBgUGx1Z2luc2Ag5LiL77yM55SxIGBTZXJ2ZXJNYW5hZ2VyLkxvYWRQbHVnaW5zRGxsKClgIOaJq+aPj+OAggotIERMTCDkuK3miYDmnInnu6fmib8gYFNlcnZlclBsdWdpbmAg5LiU6Z2e5oq96LGh55qE57G75Lya6KKr5a6e5L6L5YyW44CCCi0gRExMIOS4reaJgOaciee7p+aJvyBgQWJzdHJhY3RQcm9jZXNzQ21kYCDkuJTpnZ7mir3osaHnmoTlkb3ku6TnsbvkvJroh6rliqjms6jlhozliLAgYENtZE1hbmFnZXJg44CCCi0g5YaF572u5o+S5Lu25ZKM5aSW6YOo5o+S5Lu26YO95Lya5omn6KGMIGBJbml0aWFsaXplKClg77yb5LiW55WM5Yqg6L295ZCO5omn6KGMIGBMb2FkKClg77yM5L+d5a2Y5pe25omn6KGMIGBTYXZlKClg77yM5pyN5Yqh56uv5q+P5bin5omn6KGMIGBVcGRhdGUoZHQpYOOAggotIOS6i+S7tuiuoumYheW7uuiuruaUvuWcqCBgSW5pdGlhbGl6ZSgpYO+8jOmBv+WFjSBgTG9hZCgpYCDlpJrmrKHmiafooYzlr7zoh7Tph43lpI3orqLpmIXjgIIKLSDmj5Lku7bphY3nva7lu7rorq7lrZjliLAgYFN0b3JhZ2UuR2V0U3lzdGVtUGF0aCgiYXBwOi9Db25maWdzIilgIOaIluW9k+WJjeS4lueVjOebruW9le+8jOmBv+WFjeWGmeWFpeeoi+W6j+ebruW9leWksei0peOAggoKIyMg5pyA5bCP5o+S5Lu256S65L6LCgpgYGBjc2hhcnAKdXNpbmcgR2FtZS5TZXJ2ZXI7CgpuYW1lc3BhY2UgTXlQbHVnaW47CgpwdWJsaWMgY2xhc3MgSGVsbG9QbHVnaW4gOiBTZXJ2ZXJQbHVnaW4KewogICAgcHVibGljIG92ZXJyaWRlIGludCBWZXJzaW9uID0+IDEwMDAwOyAvLyAxLjAuMAogICAgcHVibGljIG92ZXJyaWRlIHN0cmluZyBOYW1lID0+ICLnpLrkvovmj5Lku7YiOwoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIEluaXRpYWxpemUoKQogICAgewogICAgfQoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIExvYWQoKQogICAgewogICAgfQoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIFNhdmUoKQogICAgewogICAgfQoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIFVwZGF0ZShmbG9hdCBkdCkKICAgIHsKICAgIH0KfQpgYGAKCiMjIOS6i+S7tuazqOWGjOe6puWumgoK5aSn5aSa5pWw5LqL5Lu26YO95pyJ5a+55bqU55qEIGBYeHhFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpYCDlkowgYFJlbW92ZU9iamVjdCh0aGlzKWDvvJoKCmBgYGNzaGFycApwdWJsaWMgb3ZlcnJpZGUgdm9pZCBJbml0aWFsaXplKCkKewogICAgTWVzc2FnZUV2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CiAgICBQbGF5ZXJFbnRlckdhbWVFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwp9CmBgYAoK6L+U5Zue5YC857qm5a6a77yaCgotIOWkmuaVsCBgYm9vbGAg6L+U5Zue5YC86KGo56S65piv5ZCm5YWB6K645Y6f6KGM5Li657un57ut5omn6KGM44CCCi0g6L+U5ZueIGBmYWxzZWAg6YCa5bi46KGo56S65oum5oiq44CB5Y+W5raI5oiW56aB5q2i6K+l6KGM5Li644CCCi0g566h55CG5Zmo5LiA6Iis5Lya6YGN5Y6G5omA5pyJ5aSE55CG5Zmo77yM5Y+q6KaB5Lu75oSP5aSE55CG5Zmo6L+U5ZueIGBmYWxzZWDvvIzmnIDnu4jnu5PmnpzlsLHmmK8gYGZhbHNlYOOAggotIGBGaXJzdExldmVsYCDlpJrmlbDmjqXlj6PmmoLmnKrlrozmlbTlrp7njrDmjpLluo/vvIzlu7rorq7pu5jorqTov5Tlm54gYDBg44CCCgojIyDmtojmga/kuI7lkYrnpLrniYzmjqXlj6MKCiMjIyBJTWVzc2FnZUV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuRXZlbnRgCgror6XmjqXlj6Pkv53mjIHml6fmj5Lku7blhbzlrrnvvIzlkYrnpLrniYznm7jlhbPmj5Lku7blupTnu6fnu63lrp7njrDmraTmjqXlj6PjgIIKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElNZXNzYWdlRXZlbnRIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CgogICAgdm9pZCBSZWNlaXZlTWVzc2FnZSgKICAgICAgICBzdHJpbmcgcGxheWVyTmFtZSwKICAgICAgICBOZXROb2RlIG5ldE5vZGUsCiAgICAgICAgQ2xpZW50IEZyb20sCiAgICAgICAgc3RyaW5nIG1lc3NhZ2UsCiAgICAgICAgYnl0ZSBtZXNzYWdlVHlwZSwKICAgICAgICBvdXQgYm9vbCBFeHRlcm5hbCk7CgogICAgYm9vbCBFZGl0U2lnbk1lc3NhZ2UoUG9pbnQzIHBvaW50LCBDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyKTsKfQpgYGAKCuivtOaYju+8mgoKLSBgUmVjZWl2ZU1lc3NhZ2VgIOWcqOacjeWKoeerr+aUtuWIsOiBiuWkqea2iOaBr+aXtuinpuWPkeOAggotIGBFeHRlcm5hbGAg55So5LqO5qCH6K6w5aSW6YOo5raI5oGv5pi+56S65qC35byP77yb5pen5o+S5Lu25Y+v57un57ut5L2/55So44CCCi0gYEVkaXRTaWduTWVzc2FnZWAg5Zyo546p5a626K+35rGC57yW6L6R5ZGK56S654mM5pe26Kem5Y+R77yM6L+U5ZueIGBmYWxzZWAg5Y+v6Zi75q2i5omT5byA57yW6L6R55WM6Z2i44CCCi0g5b2T5YmN54mI5pys5Li65LqG5YW85a655pen5o+S5Lu277yMYElNZXNzYWdlRXZlbnRIYW5kbGVgIOS4jeWGjeS9v+eUqCBgcmVmIHN0cmluZyBtZXNzYWdlYO+8jOS5n+S4jeWGjeaKiuWRiuekuueJjOaOpeWPo+aUueaIkCBgdm9pZGDjgIIKCuekuuS+i++8mgoKYGBgY3NoYXJwCnB1YmxpYyBjbGFzcyBTaWduR3VhcmRQbHVnaW4gOiBTZXJ2ZXJQbHVnaW4sIElNZXNzYWdlRXZlbnRIYW5kbGUKewogICAgcHVibGljIG92ZXJyaWRlIGludCBWZXJzaW9uID0+IDEwMDAwOwogICAgcHVibGljIG92ZXJyaWRlIHN0cmluZyBOYW1lID0+ICLlkYrnpLrniYzkv53miqQiOwogICAgcHVibGljIGJ5dGUgRmlyc3RMZXZlbCA9PiAwOwoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIEluaXRpYWxpemUoKQogICAgewogICAgICAgIE1lc3NhZ2VFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwogICAgfQoKICAgIHB1YmxpYyBib29sIEVkaXRTaWduTWVzc2FnZShQb2ludDMgcG9pbnQsIENvbXBvbmVudFBsYXllciBjb21wb25lbnRQbGF5ZXIpCiAgICB7CiAgICAgICAgcmV0dXJuIGNvbXBvbmVudFBsYXllciAhPSBudWxsOwogICAgfQoKICAgIHB1YmxpYyB2b2lkIFJlY2VpdmVNZXNzYWdlKHN0cmluZyBwbGF5ZXJOYW1lLCBOZXROb2RlIG5ldE5vZGUsIENsaWVudCBGcm9tLCBzdHJpbmcgbWVzc2FnZSwgYnl0ZSBtZXNzYWdlVHlwZSwgb3V0IGJvb2wgRXh0ZXJuYWwpCiAgICB7CiAgICAgICAgRXh0ZXJuYWwgPSBmYWxzZTsKICAgIH0KCiAgICBwdWJsaWMgb3ZlcnJpZGUgdm9pZCBMb2FkKCkgeyB9CiAgICBwdWJsaWMgb3ZlcnJpZGUgdm9pZCBTYXZlKCkgeyB9Cn0KYGBgCgojIyMgSU11dGFibGVNZXNzYWdlRXZlbnRIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5FdmVudGAKCuaWsOaPkuS7tuWmguaenOmcgOimgeS/ruaUueeOqeWutuiBiuWkqeWGheWuue+8jOWPr+S7pemineWkluWunueOsOivpeaJqeWxleaOpeWPo+OAguWug+S4jeS8muabv+S7oyBgSU1lc3NhZ2VFdmVudEhhbmRsZWDjgIIKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElNdXRhYmxlTWVzc2FnZUV2ZW50SGFuZGxlCnsKICAgIHZvaWQgUmVjZWl2ZU1lc3NhZ2UoCiAgICAgICAgc3RyaW5nIHBsYXllck5hbWUsCiAgICAgICAgTmV0Tm9kZSBuZXROb2RlLAogICAgICAgIENsaWVudCBGcm9tLAogICAgICAgIHJlZiBzdHJpbmcgbWVzc2FnZSwKICAgICAgICBieXRlIG1lc3NhZ2VUeXBlKTsKfQpgYGAKCuekuuS+i++8mgoKYGBgY3NoYXJwCnB1YmxpYyBjbGFzcyBXb3JkUmVwbGFjZVBsdWdpbiA6IFNlcnZlclBsdWdpbiwgSU1lc3NhZ2VFdmVudEhhbmRsZSwgSU11dGFibGVNZXNzYWdlRXZlbnRIYW5kbGUKewogICAgcHVibGljIG92ZXJyaWRlIGludCBWZXJzaW9uID0+IDEwMDAwOwogICAgcHVibGljIG92ZXJyaWRlIHN0cmluZyBOYW1lID0+ICLogYrlpKnmm7/mjaIiOwogICAgcHVibGljIGJ5dGUgRmlyc3RMZXZlbCA9PiAwOwoKICAgIHB1YmxpYyBvdmVycmlkZSB2b2lkIEluaXRpYWxpemUoKQogICAgewogICAgICAgIE1lc3NhZ2VFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwogICAgfQoKICAgIHB1YmxpYyB2b2lkIFJlY2VpdmVNZXNzYWdlKHN0cmluZyBwbGF5ZXJOYW1lLCBOZXROb2RlIG5ldE5vZGUsIENsaWVudCBGcm9tLCBzdHJpbmcgbWVzc2FnZSwgYnl0ZSBtZXNzYWdlVHlwZSwgb3V0IGJvb2wgRXh0ZXJuYWwpCiAgICB7CiAgICAgICAgRXh0ZXJuYWwgPSBmYWxzZTsKICAgIH0KCiAgICBwdWJsaWMgdm9pZCBSZWNlaXZlTWVzc2FnZShzdHJpbmcgcGxheWVyTmFtZSwgTmV0Tm9kZSBuZXROb2RlLCBDbGllbnQgRnJvbSwgcmVmIHN0cmluZyBtZXNzYWdlLCBieXRlIG1lc3NhZ2VUeXBlKQogICAgewogICAgICAgIG1lc3NhZ2UgPSBtZXNzYWdlLlJlcGxhY2UoImJhZCIsICIqKioiKTsKICAgIH0KCiAgICBwdWJsaWMgYm9vbCBFZGl0U2lnbk1lc3NhZ2UoUG9pbnQzIHBvaW50LCBDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyKSA9PiB0cnVlOwogICAgcHVibGljIG92ZXJyaWRlIHZvaWQgTG9hZCgpIHsgfQogICAgcHVibGljIG92ZXJyaWRlIHZvaWQgU2F2ZSgpIHsgfQp9CmBgYAoKIyMg546p5a626L+e5o6l5LiO6aqM6K+B5o6l5Y+jCgojIyMgSUJhbkV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXJgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJQmFuRXZlbnRIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CgogICAgYm9vbCBJc0JhbigKICAgICAgICBzdHJpbmcgbmlja25hbWUsCiAgICAgICAgc3RyaW5nIGlkLAogICAgICAgIHN0cmluZyBwYXNzd29yZCwKICAgICAgICBzdHJpbmcgaXAsCiAgICAgICAgTmV0Tm9kZSBuZXROb2RlLAogICAgICAgIENsaWVudCBjbGllbnQsCiAgICAgICAgb3V0IGJvb2wgVXNlRXh0ZXJuYWxQYXNzd29yZCk7CgogICAgYm9vbCBJc0JhbklwKHN0cmluZyBpcCwgTmV0Tm9kZSBuZXROb2RlLCBDb25uZWN0aW9uUmVxdWVzdCByZXF1ZXN0KTsKfQpgYGAKCi0gYElzQmFuYCDov5Tlm54gYHRydWVgIOihqOekuuaLkue7neeOqeWutui/m+WFpeOAggotIGBVc2VFeHRlcm5hbFBhc3N3b3JkYCDlj6/lkYrnn6XlkI7nu63lr4bnoIHpqozor4HotbDlpJbpg6jpgLvovpHjgIIKLSBgSXNCYW5JcGAg6L+U5ZueIGB0cnVlYCDooajnpLrmjIkgSVAg5ouS57ud6L+e5o6l44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApCYW5FdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIyBJUGFzc3dvcmRWYWxpZGF0aW9uRXZlbnRIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5FdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElQYXNzd29yZFZhbGlkYXRpb25FdmVudEhhbmRsZQp7CiAgICBieXRlIEZpcnN0TGV2ZWwgeyBnZXQ7IH0KCiAgICBib29sIFZhbGlkYXRlUGFzc3dvcmQoCiAgICAgICAgc3RyaW5nIGlucHV0UGFzc3dvcmQsCiAgICAgICAgc3RyaW5nIHNlcnZlclBhc3N3b3JkLAogICAgICAgIGJvb2wgdXNlRXh0ZXJuYWxQYXNzd29yZCwKICAgICAgICBzdHJpbmcgbmlja25hbWUsCiAgICAgICAgc3RyaW5nIGNvbW11bml0eUFjY291bnRJZCwKICAgICAgICBzdHJpbmcgaXBBZGRyZXNzLAogICAgICAgIE5ldE5vZGUgbmV0Tm9kZSwKICAgICAgICBDbGllbnQgY2xpZW50LAogICAgICAgIG91dCBzdHJpbmcgZXJyb3JNZXNzYWdlKTsKfQpgYGAKCi0g55So5LqO6Ieq5a6a5LmJ5a+G56CB6aqM6K+B44CCCi0g6L+U5Zue5YC86KGo56S66aqM6K+B5piv5ZCm6YCa6L+H44CCCi0gYGVycm9yTWVzc2FnZWAg5Lya5L2c5Li65aSx6LSl5o+Q56S644CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQYXNzd29yZFZhbGlkYXRpb25FdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIOeOqeWutueUn+WRveWRqOacn+aOpeWPowoKIyMjIElQbGF5ZXJFbnRlckdhbWVFdmVudEhhbmRsZQoK5ZG95ZCN56m66Ze077yaYEdhbWUuU2VydmVyLlBsYXllckV2ZW50YAoKYGBgY3NoYXJwCnB1YmxpYyBpbnRlcmZhY2UgSVBsYXllckVudGVyR2FtZUV2ZW50SGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgdm9pZCBQbGF5ZXJFbnRlcihQbGF5ZXJEYXRhIHBsYXllckRhdGEpOwogICAgdm9pZCBQbGF5ZXJMZWF2ZShQbGF5ZXJEYXRhIHBsYXllckRhdGEpOwp9CmBgYAoKLSBgUGxheWVyRW50ZXJgIOWcqOeOqeWutui/m+WFpeacjeWKoeWZqOWQjuinpuWPkeOAggotIGBQbGF5ZXJMZWF2ZWAg5Zyo546p5a6256a75byA5pyN5Yqh5Zmo5YmN6Kem5Y+R44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJFbnRlckdhbWVFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIyBJUGxheWVySW50b1BsYXlpbmdIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5QbGF5ZXJFdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElQbGF5ZXJJbnRvUGxheWluZ0hhbmRsZQp7CiAgICBieXRlIEZpcnN0TGV2ZWwgeyBnZXQ7IH0KICAgIHZvaWQgUGxheWVySW50b1BsYXlpbmdFdmVudChDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyKTsKfQpgYGAKCi0g546p5a625a6e5L2T6L+b5YWl5q2j5byPIFBsYXlpbmcg54q25oCB5pe26Kem5Y+R44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJJbnRvUGxheWluZ0V2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CmBgYAoKIyMg546p5a626KGM5Li65o6l5Y+jCgojIyMgSVBsYXllckJyZWFrQW5kUGxhY2VIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5QbGF5ZXJFdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElQbGF5ZXJCcmVha0FuZFBsYWNlSGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgYm9vbCBQbGF5ZXJQbGFjZUV2ZW50KENvbXBvbmVudFBsYXllciBjb21wb25lbnRQbGF5ZXIsIFBvaW50MyBwb2ludCwgaW50IHBsYWNlQmxvY2tWYWx1ZSk7CiAgICBib29sIFBsYXllckJyZWFrRXZlbnQoQ29tcG9uZW50UGxheWVyIGNvbXBvbmVudFBsYXllciwgUG9pbnQzIHBvaW50LCBpbnQgZGlnQmxvY2tWYWx1ZSwgaW50IHRvb2xMZXZlbCk7Cn0KYGBgCgotIOi/lOWbniBgZmFsc2VgIOWPr+mYu+atouaUvue9ruaIluegtOWdj+aWueWdl+OAggoK5rOo5YaM77yaCgpgYGBjc2hhcnAKUGxheWVyQnJlYWtBbmRQbGFjZUJsb2NrRXZlbnRNYW5hZ2VyLkFkZE9iamVjdCh0aGlzKTsKYGBgCgojIyMgSVBsYXllckludGVyYWN0RXZlbnRIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5QbGF5ZXJFdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElQbGF5ZXJJbnRlcmFjdEV2ZW50SGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgYm9vbCBJbnRlcmFjdChDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyLCBDZWxsRmFjZSBjZWxsRmFjZSk7CiAgICBib29sIFVzZShDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyLCBvYmplY3QgcmF5Y2FzdCwgaW50IGFjdGl2ZUJsb2NrVmFsdWUpOwogICAgYm9vbCBIaXQoQ29tcG9uZW50UGxheWVyIGNvbXBvbmVudFBsYXllciwgQ29tcG9uZW50Qm9keSBjb21wb25lbnRCb2R5LCBWZWN0b3IzIGhpdFBvaW50LCBWZWN0b3IzIGhpdERpcmVjdGlvbik7CiAgICBib29sIEFpbShDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyLCBpbnQgYWN0aXZlQmxvY2tWYWx1ZSwgUmF5MyBhaW0sIEFpbVN0YXRlIHN0YXRlKTsKfQpgYGAKCi0g5LiO5pa55Z2X5Lqk5LqS44CB5L2/55So54mp5ZOB44CB5Ye75omT44CB556E5YeG55u45YWz44CCCi0g6L+U5ZueIGBmYWxzZWAg5Y+v5oum5oiq5a+55bqU6KGM5Li644CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJJbnRlcmFjdEV2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CmBgYAoKIyMjIElQbGF5ZXJOZXRJbnRlcmFjdEV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuUGxheWVyRXZlbnRgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJUGxheWVyTmV0SW50ZXJhY3RFdmVudEhhbmRsZQp7CiAgICBieXRlIEZpcnN0TGV2ZWwgeyBnZXQ7IH0KCiAgICBib29sIFBsYXllck5ldEludGVyYWN0RXZlbnQoCiAgICAgICAgQ29tcG9uZW50UGxheWVyIHBsYXllciwKICAgICAgICBDb21wb25lbnRQbGF5ZXIuSW50ZXJhY3RFdmVudCBtX2ludGVyYWN0RXZlbnQsCiAgICAgICAgUmF5MyBtX25ldEludGVyYWN0UmF5LAogICAgICAgIFRlcnJhaW5SYXljYXN0UmVzdWx0PyBtX25ldEludGVyYWN0UmF5Y2FzdCk7Cn0KYGBgCgotIOWcqOaOpeaUtuWIsOeOqeWutue9kee7nOS6pOS6kuWMheaXtuinpuWPke+8jOavlOmDqOWIhuacrOWcsOS6pOS6kuaOpeWPo+abtOmdoOi/kee9kee7nOWxguOAggotIOi/lOWbniBgZmFsc2VgIOWPr+mYu+atouivpee9kee7nOS6pOS6kuOAggoK5rOo5YaM77yaCgpgYGBjc2hhcnAKUGxheWVyTmV0SW50ZXJhY3RFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIyBJUGxheWVyTW92ZUhhbmRsZQoK5ZG95ZCN56m66Ze077yaYEdhbWUuU2VydmVyLlBsYXllckV2ZW50YAoKYGBgY3NoYXJwCnB1YmxpYyBpbnRlcmZhY2UgSVBsYXllck1vdmVIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CiAgICBib29sIFBsYXllck1vdmVFdmVudChDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyLCBWZWN0b3IzIHBvc2l0aW9uKTsKfQpgYGAKCi0g546p5a6256e75Yqo5pe26Kem5Y+R44CCCi0g6L+U5ZueIGBmYWxzZWAg5Y+v6Zi75q2i56e75Yqo44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJNb3ZlRXZlbnRNYW5hZ2VyLkFkZE9iamVjdCh0aGlzKTsKYGBgCgojIyMgSVBsYXllclBvc2l0aW9uU2V0SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuUGxheWVyRXZlbnRgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJUGxheWVyUG9zaXRpb25TZXRIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CiAgICBib29sIE9uUGxheWVyUG9zaXRpb25TZXQoQ29tcG9uZW50UGxheWVyIGNvbXBvbmVudFBsYXllcik7Cn0KYGBgCgotIOeOqeWutuS9jee9ruiiq+W8uuWItuiuvue9ruaXtuinpuWPkeOAggotIOWkhOeQhuWZqOS8muaMiSBgRmlyc3RMZXZlbGAg5LuO5bCP5Yiw5aSn5o6S5bqP44CCCi0g6L+U5ZueIGBmYWxzZWAg5Y+v6Zi75q2i5L2N572u6K6+572u44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJQb3NpdGlvblNldEV2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CmBgYAoKIyMg6IOM5YyF5LiO5a655Zmo5o6l5Y+jCgojIyMgSVBsYXllckludmVudG9yeUhhbmRsZQoK5ZG95ZCN56m66Ze077yaYEdhbWUuU2VydmVyLlBsYXllckV2ZW50YAoKYGBgY3NoYXJwCnB1YmxpYyBpbnRlcmZhY2UgSVBsYXllckludmVudG9yeUhhbmRsZQp7CiAgICBieXRlIEZpcnN0TGV2ZWwgeyBnZXQ7IH0KICAgIGJvb2wgUGxheWVyRHJvcChDb21wb25lbnRQbGF5ZXIgY29tcG9uZW50UGxheWVyKTsKICAgIGJvb2wgUGxheWVyRHJhcERyb3AoQ29tcG9uZW50UGxheWVyIGNvbXBvbmVudFBsYXllciwgVmVjdG9yMyB3b3JsZFBvcywgSW52ZW50b3J5RHJhZ0RhdGEgaW52ZW50b3J5RHJhZ0RhdGEsIGludCBjb3VudCk7CiAgICBib29sIFBsYXllckhhbmRsZU1vdmVJdGVtKENvbXBvbmVudFBsYXllciBjb21wb25lbnRQbGF5ZXIsIElJbnZlbnRvcnkgc291cmNlSW52ZW50b3J5LCBpbnQgc291cmNlU2xvdEluZGV4LCBJSW52ZW50b3J5IHRhcmdldEludmVudG9yeSwgaW50IHRhcmdldFNsb3RJbmRleCwgaW50IGNvdW50KTsKICAgIGJvb2wgUGxheWVySGFuZGxlRHJhZ0Ryb3AoQ29tcG9uZW50UGxheWVyIGNvbXBvbmVudFBsYXllciwgSUludmVudG9yeSBzb3VyY2VJbnZlbnRvcnksIGludCBzb3VyY2VTbG90SW5kZXgsIERyYWdNb2RlIGRyYWdNb2RlLCBJSW52ZW50b3J5IHRhcmdldEludmVudG9yeSwgaW50IHRhcmdldFNsb3RJbmRleCwgYm9vbCBwcm9jZXNzaW5nT25seSk7Cn0KYGBgCgotIOaLpuaIqueOqeWutuS4ouW8g+OAgeaLluaLveS4ouW8g+OAgeenu+WKqOeJqeWTgeOAgeaLluaLveeJqeWTgeOAggotIOi/lOWbniBgZmFsc2VgIOWPr+mYu+atouWvueW6lOiDjOWMheaTjeS9nOOAggoK5rOo5YaM77yaCgpgYGBjc2hhcnAKUGxheWVySW52ZW50b3J5RXZlbnRNYW5hZ2VyLkFkZE9iamVjdCh0aGlzKTsKYGBgCgojIyMgSVBsYXllckludmVudG9yeU9wZW5IYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5QbGF5ZXJFdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElQbGF5ZXJJbnZlbnRvcnlPcGVuSGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgYm9vbCBQbGF5ZXJPcGVuSW52ZW50b3J5RXZlbnQoUGxheWVyRGF0YSBwbGF5ZXJEYXRhLCBJSW52ZW50b3J5IGludmVudG9yeSk7CiAgICBib29sIFBsYXllck9wZW5Qb2ludEludmVudG9yeUV2ZW50KFBsYXllckRhdGEgcGxheWVyRGF0YSwgUG9pbnQzIHBvaW50LCBJSW52ZW50b3J5IGludmVudG9yeSk7Cn0KYGBgCgotIOaJk+W8gOWuueWZqOeVjOmdouaXtuinpuWPkeOAggotIGBQbGF5ZXJPcGVuUG9pbnRJbnZlbnRvcnlFdmVudGAg5Lya6aKd5aSW5o+Q5L6b5a655Zmo5L2N572u44CCCi0g6L+U5ZueIGBmYWxzZWAg5Y+v6Zi75q2i5omT5byA44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApQbGF5ZXJJbnZlbnRvcnlPcGVuRXZlbnRNYW5hZ2VyLkFkZE9iamVjdCh0aGlzKTsKYGBgCgojIyDmlrnlnZfjgIHlnLDlvaLkuI7njq/looPmjqXlj6MKCiMjIyBJQmxvY2tDaGFuZ2VFdmVudEhhbmRsZQoK5ZG95ZCN56m66Ze077yaYEdhbWUuU2VydmVyLkV2ZW50YAoKYGBgY3NoYXJwCnB1YmxpYyBpbnRlcmZhY2UgSUJsb2NrQ2hhbmdlRXZlbnRIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CiAgICBib29sIENoYW5nZUNlbGwoaW50IHgsIGludCB5LCBpbnQgeiwgaW50IG9sZFZhbHVlLCBpbnQgbmV3VmFsdWUsIENvbXBvbmVudE1pbmVyIGNvbXBvbmVudE1pbmVyKTsKICAgIHZvaWQgT25UZXJyYWluQ29udGVudHNHZW5lcmF0ZWQoVGVycmFpblVwZGF0ZXIgdGVycmFpblVwZGF0ZXIsIFRlcnJhaW5DaHVuayBjaHVuayk7Cn0KYGBgCgotIGBDaGFuZ2VDZWxsYCDlnKjmlrnlnZflgLzlj5jljJbml7bop6blj5HvvIzov5Tlm54gYGZhbHNlYCDlj6/pmLvmraLlj5jljJbjgIIKLSBgT25UZXJyYWluQ29udGVudHNHZW5lcmF0ZWRgIOWcqOWcsOW9ouWGheWuueeUn+aIkOWQjuinpuWPke+8jOWPr+eUqOS6juiwg+aVtOWMuuWdl+WGheWuueOAggoK5rOo5YaM77yaCgpgYGBjc2hhcnAKQmxvY2tDaGFuZ2VFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIyBJRXhwbG9kZUV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuRXZlbnRgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJRXhwbG9kZUV2ZW50SGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgdm9pZCBFeHBsb2RlKGludCB4LCBpbnQgeSwgaW50IHosIHJlZiBmbG9hdCBwcmVzc3VyZSwgYm9vbCBpc0luY2VuZGlhcnksIGJvb2wgbm9FeHBsb3Npb25Tb3VuZCwgUGxheWVyRGF0YSBtaW5lcik7Cn0KYGBgCgotIOeIhueCuOaXtuinpuWPkeOAggotIOWPr+S/ruaUuSBgcHJlc3N1cmVg77yM6K6+572u5Li6IGAwYCDpgJrluLjlj6/lj5bmtojniIbngrjnoLTlnY/mlYjmnpzjgIIKCuazqOWGjO+8mgoKYGBgY3NoYXJwCkV4cGxvZGVFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIyBJRmlyZUV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuRXZlbnRgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJRmlyZUV2ZW50SGFuZGxlCnsKICAgIGJ5dGUgRmlyc3RMZXZlbCB7IGdldDsgfQogICAgYm9vbCBGaXJlKFJheTMgcmF5LCBDb21wb25lbnRNaW5lciBjb21wb25lbnRNaW5lcik7CiAgICBib29sIEZpcmVUZXJyYWluKENlbGxGYWNlIGNlbGxGYWNlLCBDb21wb25lbnRNaW5lciBjb21wb25lbnRNaW5lcik7CiAgICBib29sIE9uRmlyZUdlbmVyYXRpb24oaW50IHgsIGludCB5LCBpbnQgeiwgcmVmIGZsb2F0IHNwcmVhZGFiaWxpdHkpOwp9CmBgYAoKLSBgRmlyZWDvvJrngavmn7TlkJHlrp7kvZPnrYnnm67moIfngrnngavml7bop6blj5HjgIIKLSBgRmlyZVRlcnJhaW5g77ya54Gr5p+05ZCR5Zyw5b2i54K554Gr5pe26Kem5Y+R44CCCi0gYE9uRmlyZUdlbmVyYXRpb25g77ya54Gr5pa55Z2XL+eBq+WunuS9k+eUn+aIkOaXtuinpuWPke+8jOWPr+S/ruaUuSBgc3ByZWFkYWJpbGl0eWDjgIIKLSDov5Tlm54gYGZhbHNlYCDlj6/pmLvmraLlr7nlupTngrnngavmiJbngavnhLDnlJ/miJDjgIIKCuazqOWGjO+8mgoKYGBgY3NoYXJwCkZpcmVFdmVudE1hbmFnZXIuQWRkT2JqZWN0KHRoaXMpOwpgYGAKCiMjIOeUn+eJqeaOpeWPowoKIyMjIElDcmVhdHVyZUhlYWx0aEV2ZW50SGFuZGxlCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuRXZlbnRgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJQ3JlYXR1cmVIZWFsdGhFdmVudEhhbmRsZQp7CiAgICBieXRlIEZpcnN0TGV2ZWwgeyBnZXQ7IH0KICAgIGJvb2wgSGVhbChDb21wb25lbnRIZWFsdGggY29tcG9uZW50SGVhbHRoLCBmbG9hdCBhbW91bnQpOwogICAgYm9vbCBJbmp1cmUoQ29tcG9uZW50SGVhbHRoIGNvbXBvbmVudEhlYWx0aCwgZmxvYXQgYW1vdW50LCBDb21wb25lbnRDcmVhdHVyZSBhdHRhY2tlciwgc3RyaW5nIGNhdXNlKTsKfQpgYGAKCi0g55Sf54mp5rK755aX5oiW5Y+X5Lyk5pe26Kem5Y+R44CCCi0g6L+U5ZueIGBmYWxzZWAg5Y+v6Zi75q2i5rK755aX5oiW5Lyk5a6z44CCCgrms6jlhozvvJoKCmBgYGNzaGFycApDcmVhdHVyZUhlYWx0aEV2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CmBgYAoKIyMjIElDcmVhdHVyZVNwYXduRXZlbnRIYW5kbGUKCuWRveWQjeepuumXtO+8mmBHYW1lLlNlcnZlci5FdmVudGAKCmBgYGNzaGFycApwdWJsaWMgaW50ZXJmYWNlIElDcmVhdHVyZVNwYXduRXZlbnRIYW5kbGUKewogICAgYnl0ZSBGaXJzdExldmVsIHsgZ2V0OyB9CiAgICBib29sIFVwZGF0ZShTdWJzeXN0ZW1DcmVhdHVyZVNwYXduIHN1YnN5c3RlbUNyZWF0dXJlU3Bhd24sIGZsb2F0IGR0KTsKICAgIHZvaWQgT25FbnRpdHlBZGRlZChTdWJzeXN0ZW1DcmVhdHVyZVNwYXduIHN1YnN5c3RlbUNyZWF0dXJlU3Bhd24sIEVudGl0eSBlbnRpdHkpOwogICAgdm9pZCBPbkVudGl0eVJlbW92ZWQoU3Vic3lzdGVtQ3JlYXR1cmVTcGF3biBzdWJzeXN0ZW1DcmVhdHVyZVNwYXduLCBFbnRpdHkgZW50aXR5KTsKICAgIHZvaWQgT25QbGF5ZXJTcGF3bmVkKFBsYXllckRhdGEgcGxheWVyRGF0YSwgRW50aXR5IHBsYXllckVudGl0eSwgVmVjdG9yMyBwb3NpdGlvbik7CiAgICB2b2lkIEluaXRDcmVhdHVyZVR5cGVzKFN1YnN5c3RlbUNyZWF0dXJlU3Bhd24gc3Vic3lzdGVtQ3JlYXR1cmVTcGF3biwgTGlzdDxTdWJzeXN0ZW1DcmVhdHVyZVNwYXduLkNyZWF0dXJlVHlwZT4gY3JlYXR1cmVUeXBlcyk7Cn0KYGBgCgotIOWPr+WPguS4jueUn+eJqeWIt+aWsOOAgeWunuS9k+a3u+WKoOenu+mZpOOAgeeOqeWutuWHuueUn+OAgeeUn+eJqeexu+Wei+WIneWni+WMluOAggotIGBVcGRhdGVgIOi/lOWbniBgZmFsc2VgIOWPr+mYu+atouWOn+WIt+aWsOabtOaWsOmAu+i+kee7p+e7reaJp+ihjOOAggoK5rOo5YaM77yaCgpgYGBjc2hhcnAKQ3JlYXR1cmVTcGF3bkV2ZW50TWFuYWdlci5BZGRPYmplY3QodGhpcyk7CmBgYAoKIyMg5pyN5Yqh5Zmo5ZG95Luk5o6l5Y+jCgrlkb3ku6Tnsbvnu6fmib8gYEdhbWUuU2VydmVyLkFic3RyYWN0UHJvY2Vzc0NtZGDjgILlpJbpg6ggRExMIOS4remdnuaKveixoeWRveS7pOexu+S8muiHquWKqOazqOWGjOOAggoKYGBgY3NoYXJwCnB1YmxpYyBhYnN0cmFjdCBjbGFzcyBBYnN0cmFjdFByb2Nlc3NDbWQKewogICAgcHVibGljIGFic3RyYWN0IHN0cmluZyBDbWQgeyBnZXQ7IH0KICAgIHB1YmxpYyBhYnN0cmFjdCBzdHJpbmcgSW50cm9kdWNlIHsgZ2V0OyB9CiAgICBwdWJsaWMgYWJzdHJhY3QgaW50IEF1dGhMZXZlbCB7IGdldDsgfQogICAgcHVibGljIGFic3RyYWN0IERpc3BsYXlUeXBlIERpc3BsYXkgeyBnZXQ7IH0KICAgIHB1YmxpYyBhYnN0cmFjdCB2b2lkIFByb2Nlc3NDbWQoKTsKICAgIHB1YmxpYyB2aXJ0dWFsIElFbnVtZXJhYmxlPHN0cmluZz4gR2V0U3VnZ2VzdGlvbnMoc3RyaW5nW10gYXJncyk7Cn0KYGBgCgpgRGlzcGxheVR5cGVg77yaCgotIGBBbGxg77ya5omA5pyJ5Lq66YO95Y+v5Lul5Zyo5biu5Yqp5Lit55yL5Yiw44CCCi0gYEF1dGhvcml0eWDvvJrmnYPpmZDovr7liLDlkI7lj6/op4HjgIIKLSBgTm9EaXNwbGF5YO+8muS4jeWcqOW4ruWKqeS4reaYvuekuuOAggoK5ZG95Luk56S65L6L77yaCgpgYGBjc2hhcnAKcHVibGljIGNsYXNzIENtZEhlbGxvIDogQWJzdHJhY3RQcm9jZXNzQ21kCnsKICAgIHB1YmxpYyBvdmVycmlkZSBzdHJpbmcgQ21kID0+ICJoZWxsbyI7CiAgICBwdWJsaWMgb3ZlcnJpZGUgc3RyaW5nIEludHJvZHVjZSA9PiAiL2hlbGxvIC0g5rWL6K+V5ZG95LukIjsKICAgIHB1YmxpYyBvdmVycmlkZSBpbnQgQXV0aExldmVsID0+IDA7CiAgICBwdWJsaWMgb3ZlcnJpZGUgRGlzcGxheVR5cGUgRGlzcGxheSA9PiBEaXNwbGF5VHlwZS5BbGw7CgogICAgcHVibGljIG92ZXJyaWRlIHZvaWQgUHJvY2Vzc0NtZCgpCiAgICB7CiAgICAgICAgU2VuZE1lc3NhZ2UoIkhlbGxvIiwgIuaPkuS7tuWRveS7pOaJp+ihjOaIkOWKnyIpOwogICAgfQp9CmBgYAoKIyMg57uI56uv5o+Q56S656ym5LiO54q25oCB5qCP5o6l5Y+jCgrov5nkupvmjqXlj6PnlKjkuo7lop7lvLrmnI3liqHnq6/nu4jnq6/mmL7npLrvvIzlj6rmnInlop7lvLrnu4jnq6/mqKHlvI/mlK/mjIHvvJvmma7pgJrnu4jnq6/kuIvkvJrkvb/nlKggYE51bGxQcm9tcHRNYW5hZ2VyYCAvIGBOdWxsU3RhdHVzTWFuYWdlcmAg6ZmN57qn44CCCgojIyMgSVByb21wdFByb3ZpZGVyCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuVGVybWluYWxgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJUHJvbXB0UHJvdmlkZXIKewogICAgaW50IFByaW9yaXR5IHsgZ2V0OyB9CiAgICBJRW51bWVyYWJsZTxDb25zb2xlUGFydD4gR2V0UHJvbXB0UGFydHMoVGVybWluYWxDb250ZXh0Rm9yUGFydCBjdHgpOwogICAgZmxvYXQgUmVmcmVzaEludGVydmFsID0+IDEwZjsKfQpgYGAKCuazqOWGjO+8mgoKYGBgY3NoYXJwClRlcm1pbmFsTWFuYWdlci5SZWdpc3RlclByb21wdChuZXcgTXlQcm9tcHRQcm92aWRlcigpKTsKYGBgCgojIyMgSVN0YXR1c1Byb3ZpZGVyCgrlkb3lkI3nqbrpl7TvvJpgR2FtZS5TZXJ2ZXIuVGVybWluYWxgCgpgYGBjc2hhcnAKcHVibGljIGludGVyZmFjZSBJU3RhdHVzUHJvdmlkZXIKewogICAgaW50IFByaW9yaXR5IHsgZ2V0OyB9CiAgICBJRW51bWVyYWJsZTxDb25zb2xlUGFydD4gR2V0U3RhdHVzUGFydHMoVGVybWluYWxDb250ZXh0Rm9yUGFydCBjdHgpOwogICAgZmxvYXQgUmVmcmVzaEludGVydmFsID0+IDEwZjsKICAgIGJvb2wgSXNGdWxsTGluZSA9PiBmYWxzZTsKfQpgYGAKCuazqOWGjO+8mgoKYGBgY3NoYXJwClRlcm1pbmFsTWFuYWdlci5SZWdpc3RlclN0YXR1cyhuZXcgTXlTdGF0dXNQcm92aWRlcigpKTsKYGBgCgojIyDmj5Lku7blt6XnqIvlu7rorq4KCuWklumDqOaPkuS7tumhueebrumAmuW4uOW8leeUqO+8mgoKYGBgeG1sCjxQcm9qZWN0UmVmZXJlbmNlIEluY2x1ZGU9Ii4uXC4uXEVuZ2luZVNlcnZlclxFbmdpbmVTZXJ2ZXIuY3Nwcm9qIiAvPgo8UHJvamVjdFJlZmVyZW5jZSBJbmNsdWRlPSIuLlwuLlxFbnRpdHlTeXN0ZW1TZXJ2ZXJcRW50aXR5U3lzdGVtU2VydmVyLmNzcHJvaiIgLz4KPFByb2plY3RSZWZlcmVuY2UgSW5jbHVkZT0iLi5cLi5cU3Vydml2YWxjcmFmdFNlcnZlclxTdXJ2aXZhbGNyYWZ0U2VydmVyLmNzcHJvaiIgLz4KYGBgCgrlu7rorq7vvJoKCi0g5o+S5Lu2IERMTCDmlL7liLDmnI3liqHnq68gYFBsdWdpbnNgIOebruW9leOAggotIOaPkuS7tuexu+WSjOWRveS7pOexu+mDveW/hemhu+aYryBgcHVibGljYCDkuJTpnZ7mir3osaHnsbvjgIIKLSDorqLpmIXkuovku7bmlL7lnKggYEluaXRpYWxpemUoKWDjgIIKLSDphY3nva7or7vlj5bmlL7lnKggYExvYWQoKWDvvIzkv53lrZjmlL7lnKggYFNhdmUoKWDjgIIKLSBgVXBkYXRlKGR0KWAg5Lit6YG/5YWN6Zi75aGeIElP44CB572R57uc6K+35rGC5ZKM6ZW/6ICX5pe26K6h566X44CCCi0g6ZyA6KaB5LiW55WM6Lev5b6E5pe277yM5Y+v5Y+C6ICD566x5a2Q6ZSB5o+S5Lu25oyJ5b2T5YmN5LiW55WM55uu5b2V5L+d5a2Y6YWN572u44CCCi0g5LiN6KaB5Zyo5pel5b+X5Lit5omT5Y2w5pWP5oSfIHRva2Vu44CB5a+G56CB44CB546p5a626ZqQ56eB5L+h5oGv44CCCgojIyDlvZPliY3lhbzlrrnmgKfor7TmmI4KCi0gYElNZXNzYWdlRXZlbnRIYW5kbGVgIOW3suaBouWkjeaXp+etvuWQje+8jOaXp+WRiuekuueJjC/ogYrlpKnmj5Lku7bkvJjlhYjlhbzlrrnjgIIKLSDmlrDnmoTogYrlpKnlhoXlrrnkv67mlLnog73lipvpgJrov4cgYElNdXRhYmxlTWVzc2FnZUV2ZW50SGFuZGxlYCDmianlsZXlrp7njrDjgIIKLSDoi6Xmj5Lku7bmj5DnpLrmib7kuI3liLDmjqXlj6PvvIzkvJjlhYjmo4Dmn6XlvJXnlKjnmoTmnI3liqHnq6/nqIvluo/pm4bniYjmnKzvvIzku6Xlj4rmmK/lkKbmt7vliqDmraPnoa7lkb3lkI3nqbrpl7TvvJoKCmBgYGNzaGFycAp1c2luZyBHYW1lLlNlcnZlci5FdmVudDsKdXNpbmcgR2FtZS5TZXJ2ZXIuUGxheWVyRXZlbnQ7CmBgYAo=
