From d105e4d50c4d78e06508c3c0b68491b567a1d1f1 Mon Sep 17 00:00:00 2001 From: Xel Date: Fri, 27 May 2011 19:24:09 +0300 Subject: [PATCH] orig source.. nothing changed here.. --- license.txt | 1 + make.bat | 90 + readme | 1 + resources/Compile-Scripts.bat | 32 + resources/FindJDK.bat | 35 + resources/Manifest.txt | 9 + resources/compile-scripts.sh | 33 + resources/lib/xel.jar | Bin 0 -> 293671 bytes resources/version.txt | 1 + src/org/rsbot/Application.java | 141 + src/org/rsbot/Boot.java | 71 + src/org/rsbot/Configuration.java | 342 +++ src/org/rsbot/bot/Bot.java | 274 ++ src/org/rsbot/bot/BotStub.java | 143 + src/org/rsbot/bot/CallbackImpl.java | 38 + src/org/rsbot/bot/Crawler.java | 96 + src/org/rsbot/bot/RSClassLoader.java | 107 + src/org/rsbot/bot/RSLoader.java | 178 ++ src/org/rsbot/client/Cache.java | 12 + src/org/rsbot/client/Callback.java | 14 + src/org/rsbot/client/ChatLine.java | 9 + src/org/rsbot/client/Client.java | 156 + src/org/rsbot/client/DefLoader.java | 7 + src/org/rsbot/client/DetailInfo.java | 5 + src/org/rsbot/client/DetailInfoNode.java | 5 + src/org/rsbot/client/Graphic.java | 7 + src/org/rsbot/client/HDModel.java | 5 + src/org/rsbot/client/HardReference.java | 7 + src/org/rsbot/client/HashTable.java | 7 + src/org/rsbot/client/LDModel.java | 5 + src/org/rsbot/client/Loader.java | 7 + src/org/rsbot/client/MenuGroupNode.java | 14 + src/org/rsbot/client/MenuItemNode.java | 11 + src/org/rsbot/client/Model.java | 15 + src/org/rsbot/client/ModelCapture.java | 72 + src/org/rsbot/client/Node.java | 11 + src/org/rsbot/client/NodeDeque.java | 9 + src/org/rsbot/client/NodeListCache.java | 7 + src/org/rsbot/client/NodeSub.java | 9 + src/org/rsbot/client/NodeSubQueue.java | 9 + src/org/rsbot/client/RSAnimable.java | 13 + src/org/rsbot/client/RSAnimableNode.java | 9 + src/org/rsbot/client/RSCharacter.java | 29 + src/org/rsbot/client/RSGround.java | 27 + src/org/rsbot/client/RSGroundData.java | 11 + src/org/rsbot/client/RSGroundEntity.java | 7 + src/org/rsbot/client/RSGroundObject.java | 11 + src/org/rsbot/client/RSInteractable.java | 10 + src/org/rsbot/client/RSInteractableDef.java | 7 + src/org/rsbot/client/RSInterface.java | 99 + src/org/rsbot/client/RSInterfaceNode.java | 7 + src/org/rsbot/client/RSItem.java | 9 + src/org/rsbot/client/RSItemDef.java | 20 + src/org/rsbot/client/RSItemDefLoader.java | 7 + src/org/rsbot/client/RSNPC.java | 9 + src/org/rsbot/client/RSNPCDef.java | 11 + src/org/rsbot/client/RSNPCDefLoader.java | 5 + src/org/rsbot/client/RSNPCNode.java | 5 + src/org/rsbot/client/RSObject.java | 15 + src/org/rsbot/client/RSObjectComposite.java | 5 + src/org/rsbot/client/RSObjectDef.java | 11 + src/org/rsbot/client/RSObjectDefLoader.java | 5 + src/org/rsbot/client/RSPlayer.java | 13 + src/org/rsbot/client/RSPlayerComposite.java | 10 + src/org/rsbot/client/RandomAccessFile.java | 208 ++ src/org/rsbot/client/Reference.java | 7 + src/org/rsbot/client/Render.java | 24 + src/org/rsbot/client/RenderData.java | 35 + src/org/rsbot/client/ServerData.java | 7 + src/org/rsbot/client/Settings.java | 7 + src/org/rsbot/client/Signlink.java | 11 + src/org/rsbot/client/SoftReference.java | 7 + src/org/rsbot/client/StatusNode.java | 9 + src/org/rsbot/client/StatusNodeList.java | 9 + .../rsbot/client/StatusNodeListLoader.java | 5 + src/org/rsbot/client/TileData.java | 7 + src/org/rsbot/client/input/Canvas.java | 126 + src/org/rsbot/client/input/Focus.java | 20 + src/org/rsbot/client/input/Keyboard.java | 33 + src/org/rsbot/client/input/Mouse.java | 168 ++ src/org/rsbot/event/EventManager.java | 209 ++ src/org/rsbot/event/EventMulticaster.java | 326 +++ .../event/events/CharacterMovedEvent.java | 65 + src/org/rsbot/event/events/MessageEvent.java | 64 + src/org/rsbot/event/events/PaintEvent.java | 54 + src/org/rsbot/event/events/RSEvent.java | 20 + .../rsbot/event/events/TextPaintEvent.java | 60 + .../event/impl/CharacterMovedLogger.java | 16 + src/org/rsbot/event/impl/DrawBoundaries.java | 113 + src/org/rsbot/event/impl/DrawGround.java | 43 + src/org/rsbot/event/impl/DrawInventory.java | 37 + src/org/rsbot/event/impl/DrawItems.java | 63 + src/org/rsbot/event/impl/DrawModel.java | 142 + src/org/rsbot/event/impl/DrawMouse.java | 110 + src/org/rsbot/event/impl/DrawNPCs.java | 53 + src/org/rsbot/event/impl/DrawObjects.java | 73 + src/org/rsbot/event/impl/DrawPlayers.java | 60 + src/org/rsbot/event/impl/DrawSettings.java | 61 + src/org/rsbot/event/impl/DrawWeb.java | 64 + src/org/rsbot/event/impl/MessageLogger.java | 20 + src/org/rsbot/event/impl/TAnimation.java | 30 + src/org/rsbot/event/impl/TCamera.java | 29 + src/org/rsbot/event/impl/TFPS.java | 32 + src/org/rsbot/event/impl/TFloorHeight.java | 25 + src/org/rsbot/event/impl/TLoginIndex.java | 24 + src/org/rsbot/event/impl/TMenu.java | 29 + src/org/rsbot/event/impl/TMenuActions.java | 27 + src/org/rsbot/event/impl/TMousePosition.java | 31 + src/org/rsbot/event/impl/TPlayerPosition.java | 26 + src/org/rsbot/event/impl/TTab.java | 27 + .../rsbot/event/impl/TUserInputAllowed.java | 24 + src/org/rsbot/event/impl/TWebStatus.java | 26 + .../listeners/CharacterMovedListener.java | 15 + .../event/listeners/MessageListener.java | 9 + .../rsbot/event/listeners/PaintListener.java | 8 + .../event/listeners/TextPaintListener.java | 13 + src/org/rsbot/gui/AccountManager.java | 419 +++ src/org/rsbot/gui/BotGUI.java | 663 +++++ src/org/rsbot/gui/BotHome.java | 102 + src/org/rsbot/gui/BotMenuBar.java | 278 ++ src/org/rsbot/gui/BotPanel.java | 224 ++ src/org/rsbot/gui/BotToolBar.java | 469 +++ src/org/rsbot/gui/ScriptSelector.java | 483 ++++ src/org/rsbot/gui/SettingsManager.java | 335 +++ src/org/rsbot/gui/SplashAd.java | 153 + .../rsbot/gui/component/JComboCheckBox.java | 100 + src/org/rsbot/gui/component/LogTextArea.java | 245 ++ src/org/rsbot/gui/component/Messages.java | 54 + src/org/rsbot/loader/ClientLoader.java | 194 ++ src/org/rsbot/loader/VersionVisitor.java | 188 ++ .../rsbot/loader/asm/AnnotationVisitor.java | 73 + .../rsbot/loader/asm/AnnotationWriter.java | 293 ++ src/org/rsbot/loader/asm/Attribute.java | 226 ++ src/org/rsbot/loader/asm/ByteVector.java | 268 ++ src/org/rsbot/loader/asm/ClassAdapter.java | 101 + src/org/rsbot/loader/asm/ClassReader.java | 2029 +++++++++++++ src/org/rsbot/loader/asm/ClassVisitor.java | 172 ++ src/org/rsbot/loader/asm/ClassWriter.java | 1266 ++++++++ src/org/rsbot/loader/asm/Edge.java | 51 + src/org/rsbot/loader/asm/FieldVisitor.java | 40 + src/org/rsbot/loader/asm/FieldWriter.java | 243 ++ src/org/rsbot/loader/asm/Frame.java | 1136 ++++++++ src/org/rsbot/loader/asm/Handler.java | 46 + src/org/rsbot/loader/asm/Item.java | 229 ++ src/org/rsbot/loader/asm/Label.java | 496 ++++ src/org/rsbot/loader/asm/MethodAdapter.java | 186 ++ src/org/rsbot/loader/asm/MethodVisitor.java | 375 +++ src/org/rsbot/loader/asm/MethodWriter.java | 2533 +++++++++++++++++ src/org/rsbot/loader/asm/Opcodes.java | 323 +++ src/org/rsbot/loader/asm/Type.java | 750 +++++ src/org/rsbot/loader/script/Buffer.java | 58 + src/org/rsbot/loader/script/CodeReader.java | 110 + src/org/rsbot/loader/script/ModScript.java | 207 ++ .../rsbot/loader/script/ParseException.java | 17 + .../script/adapter/AddFieldAdapter.java | 32 + .../script/adapter/AddGetterAdapter.java | 114 + .../script/adapter/AddInterfaceAdapter.java | 32 + .../script/adapter/AddMethodAdapter.java | 41 + .../script/adapter/InsertCodeAdapter.java | 213 ++ .../script/adapter/OverrideClassAdapter.java | 187 ++ .../script/adapter/SetSignatureAdapter.java | 47 + .../script/adapter/SetSuperAdapter.java | 51 + src/org/rsbot/log/LogFormatter.java | 61 + src/org/rsbot/log/LogOutputStream.java | 153 + src/org/rsbot/log/SystemConsoleHandler.java | 13 + src/org/rsbot/log/TextAreaLogHandler.java | 25 + src/org/rsbot/script/AccountStore.java | 254 ++ src/org/rsbot/script/BackgroundScript.java | 109 + src/org/rsbot/script/Random.java | 155 + src/org/rsbot/script/Script.java | 364 +++ src/org/rsbot/script/ScriptManifest.java | 23 + .../rsbot/script/background/BankMonitor.java | 50 + src/org/rsbot/script/background/WebData.java | 86 + .../rsbot/script/background/WebLoader.java | 78 + .../internal/BackgroundScriptHandler.java | 90 + .../rsbot/script/internal/BreakHandler.java | 64 + .../rsbot/script/internal/InputManager.java | 374 +++ .../rsbot/script/internal/MouseHandler.java | 315 ++ .../rsbot/script/internal/ScriptHandler.java | 210 ++ .../script/internal/event/ScriptListener.java | 22 + .../script/internal/reflection/Hook.java | 38 + .../script/internal/reflection/Hooks.java | 94 + .../internal/reflection/Reflection.java | 268 ++ .../rsbot/script/internal/wrappers/Deque.java | 63 + .../script/internal/wrappers/HashTable.java | 33 + .../rsbot/script/internal/wrappers/Queue.java | 51 + .../script/internal/wrappers/StatusQueue.java | 72 + .../script/internal/wrappers/TileData.java | 33 + .../script/internal/wrappers/WebAction.java | 5 + src/org/rsbot/script/methods/Account.java | 48 + src/org/rsbot/script/methods/Bank.java | 795 ++++++ .../rsbot/script/methods/Calculations.java | 556 ++++ src/org/rsbot/script/methods/Camera.java | 331 +++ src/org/rsbot/script/methods/ClanChat.java | 196 ++ src/org/rsbot/script/methods/Combat.java | 200 ++ src/org/rsbot/script/methods/Environment.java | 123 + src/org/rsbot/script/methods/Equipment.java | 155 + src/org/rsbot/script/methods/FriendChat.java | 590 ++++ src/org/rsbot/script/methods/Game.java | 744 +++++ src/org/rsbot/script/methods/GameGUI.java | 144 + .../rsbot/script/methods/GrandExchange.java | 429 +++ src/org/rsbot/script/methods/GroundItems.java | 196 ++ src/org/rsbot/script/methods/Hiscores.java | 170 ++ src/org/rsbot/script/methods/Interfaces.java | 327 +++ src/org/rsbot/script/methods/Inventory.java | 667 +++++ src/org/rsbot/script/methods/Keyboard.java | 60 + src/org/rsbot/script/methods/Lobby.java | 253 ++ src/org/rsbot/script/methods/Magic.java | 364 +++ src/org/rsbot/script/methods/Menu.java | 385 +++ .../rsbot/script/methods/MethodContext.java | 217 ++ .../rsbot/script/methods/MethodProvider.java | 92 + src/org/rsbot/script/methods/Methods.java | 340 +++ src/org/rsbot/script/methods/Mouse.java | 496 ++++ src/org/rsbot/script/methods/NPCs.java | 180 ++ src/org/rsbot/script/methods/Nodes.java | 49 + src/org/rsbot/script/methods/Objects.java | 316 ++ src/org/rsbot/script/methods/Players.java | 129 + src/org/rsbot/script/methods/Prayer.java | 304 ++ src/org/rsbot/script/methods/Quests.java | 146 + src/org/rsbot/script/methods/Settings.java | 59 + src/org/rsbot/script/methods/Skills.java | 539 ++++ src/org/rsbot/script/methods/Store.java | 180 ++ src/org/rsbot/script/methods/Summoning.java | 358 +++ src/org/rsbot/script/methods/Tiles.java | 139 + src/org/rsbot/script/methods/Trade.java | 260 ++ src/org/rsbot/script/methods/Walking.java | 300 ++ src/org/rsbot/script/methods/Web.java | 419 +++ .../script/provider/FileScriptSource.java | 167 ++ .../script/provider/ScriptClassLoader.java | 65 + .../script/provider/ScriptDefinition.java | 63 + .../provider/ScriptDeliveryNetwork.java | 134 + .../script/provider/ScriptDownloader.java | 208 ++ src/org/rsbot/script/provider/ScriptList.java | 49 + .../rsbot/script/provider/ScriptSource.java | 17 + src/org/rsbot/script/randoms/BankPins.java | 65 + .../rsbot/script/randoms/BeehiveSolver.java | 204 ++ src/org/rsbot/script/randoms/CapnArnav.java | 190 ++ src/org/rsbot/script/randoms/Certer.java | 127 + .../script/randoms/CloseAllInterface.java | 100 + src/org/rsbot/script/randoms/DrillDemon.java | 325 +++ src/org/rsbot/script/randoms/Exam.java | 499 ++++ .../rsbot/script/randoms/FirstTimeDeath.java | 75 + .../rsbot/script/randoms/FreakyForester.java | 328 +++ src/org/rsbot/script/randoms/FrogCave.java | 115 + src/org/rsbot/script/randoms/GraveDigger.java | 354 +++ .../script/randoms/ImprovedRewardsBox.java | 290 ++ .../rsbot/script/randoms/LeaveSafeArea.java | 29 + src/org/rsbot/script/randoms/LoginBot.java | 339 +++ .../rsbot/script/randoms/LostAndFound.java | 128 + src/org/rsbot/script/randoms/Maze.java | 406 +++ src/org/rsbot/script/randoms/Mime.java | 142 + src/org/rsbot/script/randoms/Molly.java | 252 ++ src/org/rsbot/script/randoms/Pillory.java | 202 ++ src/org/rsbot/script/randoms/Pinball.java | 95 + src/org/rsbot/script/randoms/Prison.java | 497 ++++ src/org/rsbot/script/randoms/QuizSolver.java | 188 ++ .../rsbot/script/randoms/SandwhichLady.java | 115 + .../rsbot/script/randoms/ScapeRuneIsland.java | 235 ++ .../rsbot/script/randoms/SystemUpdate.java | 68 + .../rsbot/script/randoms/TeleotherCloser.java | 24 + src/org/rsbot/script/util/BankCache.java | 102 + src/org/rsbot/script/util/Filter.java | 21 + src/org/rsbot/script/util/SkillData.java | 95 + src/org/rsbot/script/util/Timer.java | 128 + src/org/rsbot/script/util/WindowUtil.java | 179 ++ src/org/rsbot/script/web/PlaneHandler.java | 34 + src/org/rsbot/script/web/PlaneTraverse.java | 42 + src/org/rsbot/script/web/Prerequisites.java | 10 + src/org/rsbot/script/web/Route.java | 42 + src/org/rsbot/script/web/RouteStep.java | 117 + src/org/rsbot/script/web/Teleport.java | 23 + src/org/rsbot/script/web/Transportation.java | 16 + .../script/web/TransportationHandler.java | 119 + .../rsbot/script/web/methods/Equipment.java | 59 + .../script/web/methods/TeleportItem.java | 98 + .../script/web/methods/TeleportJewelry.java | 121 + .../rsbot/script/web/methods/TeleportNPC.java | 46 + .../script/web/methods/TeleportObject.java | 63 + .../script/web/methods/TeleportRunes.java | 112 + .../script/wrappers/RSAnimableModel.java | 32 + src/org/rsbot/script/wrappers/RSArea.java | 239 ++ .../rsbot/script/wrappers/RSCharacter.java | 238 ++ .../script/wrappers/RSCharacterModel.java | 56 + .../rsbot/script/wrappers/RSComponent.java | 943 ++++++ .../rsbot/script/wrappers/RSGroundItem.java | 88 + .../rsbot/script/wrappers/RSInterface.java | 281 ++ src/org/rsbot/script/wrappers/RSItem.java | 181 ++ src/org/rsbot/script/wrappers/RSItemDef.java | 28 + .../rsbot/script/wrappers/RSLocalPath.java | 322 +++ src/org/rsbot/script/wrappers/RSModel.java | 339 +++ src/org/rsbot/script/wrappers/RSNPC.java | 103 + src/org/rsbot/script/wrappers/RSObject.java | 240 ++ .../rsbot/script/wrappers/RSObjectDef.java | 29 + .../rsbot/script/wrappers/RSObjectModel.java | 29 + src/org/rsbot/script/wrappers/RSPath.java | 86 + src/org/rsbot/script/wrappers/RSPlayer.java | 59 + .../rsbot/script/wrappers/RSStaticModel.java | 29 + src/org/rsbot/script/wrappers/RSTile.java | 88 + src/org/rsbot/script/wrappers/RSTilePath.java | 137 + .../rsbot/script/wrappers/RSVerifiable.java | 7 + src/org/rsbot/script/wrappers/RSWeb.java | 69 + src/org/rsbot/script/wrappers/RSWebTile.java | 46 + .../security/RestrictedSecurityManager.java | 402 +++ src/org/rsbot/service/DRM.java | 53 + src/org/rsbot/service/Monitoring.java | 214 ++ src/org/rsbot/service/ServiceException.java | 17 + src/org/rsbot/service/TwitterUpdates.java | 76 + src/org/rsbot/service/WebQueue.java | 240 ++ src/org/rsbot/util/ApplicationException.java | 9 + src/org/rsbot/util/Base64.java | 1078 +++++++ src/org/rsbot/util/StringUtil.java | 146 + src/org/rsbot/util/UpdateChecker.java | 91 + src/org/rsbot/util/io/HttpClient.java | 157 + src/org/rsbot/util/io/IOHelper.java | 74 + src/org/rsbot/util/io/IniParser.java | 109 + src/org/rsbot/util/io/JavaCompiler.java | 98 + src/org/rsbot/util/io/PreferenceData.java | 93 + src/org/rsbot/util/io/ScreenshotUtil.java | 72 + src/org/rsbot/util/io/UIDData.java | 84 + 319 files changed, 49476 insertions(+) create mode 100644 license.txt create mode 100644 make.bat create mode 100644 readme create mode 100644 resources/Compile-Scripts.bat create mode 100644 resources/FindJDK.bat create mode 100644 resources/Manifest.txt create mode 100644 resources/compile-scripts.sh create mode 100644 resources/lib/xel.jar create mode 100644 resources/version.txt create mode 100644 src/org/rsbot/Application.java create mode 100644 src/org/rsbot/Boot.java create mode 100644 src/org/rsbot/Configuration.java create mode 100644 src/org/rsbot/bot/Bot.java create mode 100644 src/org/rsbot/bot/BotStub.java create mode 100644 src/org/rsbot/bot/CallbackImpl.java create mode 100644 src/org/rsbot/bot/Crawler.java create mode 100644 src/org/rsbot/bot/RSClassLoader.java create mode 100644 src/org/rsbot/bot/RSLoader.java create mode 100644 src/org/rsbot/client/Cache.java create mode 100644 src/org/rsbot/client/Callback.java create mode 100644 src/org/rsbot/client/ChatLine.java create mode 100644 src/org/rsbot/client/Client.java create mode 100644 src/org/rsbot/client/DefLoader.java create mode 100644 src/org/rsbot/client/DetailInfo.java create mode 100644 src/org/rsbot/client/DetailInfoNode.java create mode 100644 src/org/rsbot/client/Graphic.java create mode 100644 src/org/rsbot/client/HDModel.java create mode 100644 src/org/rsbot/client/HardReference.java create mode 100644 src/org/rsbot/client/HashTable.java create mode 100644 src/org/rsbot/client/LDModel.java create mode 100644 src/org/rsbot/client/Loader.java create mode 100644 src/org/rsbot/client/MenuGroupNode.java create mode 100644 src/org/rsbot/client/MenuItemNode.java create mode 100644 src/org/rsbot/client/Model.java create mode 100644 src/org/rsbot/client/ModelCapture.java create mode 100644 src/org/rsbot/client/Node.java create mode 100644 src/org/rsbot/client/NodeDeque.java create mode 100644 src/org/rsbot/client/NodeListCache.java create mode 100644 src/org/rsbot/client/NodeSub.java create mode 100644 src/org/rsbot/client/NodeSubQueue.java create mode 100644 src/org/rsbot/client/RSAnimable.java create mode 100644 src/org/rsbot/client/RSAnimableNode.java create mode 100644 src/org/rsbot/client/RSCharacter.java create mode 100644 src/org/rsbot/client/RSGround.java create mode 100644 src/org/rsbot/client/RSGroundData.java create mode 100644 src/org/rsbot/client/RSGroundEntity.java create mode 100644 src/org/rsbot/client/RSGroundObject.java create mode 100644 src/org/rsbot/client/RSInteractable.java create mode 100644 src/org/rsbot/client/RSInteractableDef.java create mode 100644 src/org/rsbot/client/RSInterface.java create mode 100644 src/org/rsbot/client/RSInterfaceNode.java create mode 100644 src/org/rsbot/client/RSItem.java create mode 100644 src/org/rsbot/client/RSItemDef.java create mode 100644 src/org/rsbot/client/RSItemDefLoader.java create mode 100644 src/org/rsbot/client/RSNPC.java create mode 100644 src/org/rsbot/client/RSNPCDef.java create mode 100644 src/org/rsbot/client/RSNPCDefLoader.java create mode 100644 src/org/rsbot/client/RSNPCNode.java create mode 100644 src/org/rsbot/client/RSObject.java create mode 100644 src/org/rsbot/client/RSObjectComposite.java create mode 100644 src/org/rsbot/client/RSObjectDef.java create mode 100644 src/org/rsbot/client/RSObjectDefLoader.java create mode 100644 src/org/rsbot/client/RSPlayer.java create mode 100644 src/org/rsbot/client/RSPlayerComposite.java create mode 100644 src/org/rsbot/client/RandomAccessFile.java create mode 100644 src/org/rsbot/client/Reference.java create mode 100644 src/org/rsbot/client/Render.java create mode 100644 src/org/rsbot/client/RenderData.java create mode 100644 src/org/rsbot/client/ServerData.java create mode 100644 src/org/rsbot/client/Settings.java create mode 100644 src/org/rsbot/client/Signlink.java create mode 100644 src/org/rsbot/client/SoftReference.java create mode 100644 src/org/rsbot/client/StatusNode.java create mode 100644 src/org/rsbot/client/StatusNodeList.java create mode 100644 src/org/rsbot/client/StatusNodeListLoader.java create mode 100644 src/org/rsbot/client/TileData.java create mode 100644 src/org/rsbot/client/input/Canvas.java create mode 100644 src/org/rsbot/client/input/Focus.java create mode 100644 src/org/rsbot/client/input/Keyboard.java create mode 100644 src/org/rsbot/client/input/Mouse.java create mode 100644 src/org/rsbot/event/EventManager.java create mode 100644 src/org/rsbot/event/EventMulticaster.java create mode 100644 src/org/rsbot/event/events/CharacterMovedEvent.java create mode 100644 src/org/rsbot/event/events/MessageEvent.java create mode 100644 src/org/rsbot/event/events/PaintEvent.java create mode 100644 src/org/rsbot/event/events/RSEvent.java create mode 100644 src/org/rsbot/event/events/TextPaintEvent.java create mode 100644 src/org/rsbot/event/impl/CharacterMovedLogger.java create mode 100644 src/org/rsbot/event/impl/DrawBoundaries.java create mode 100644 src/org/rsbot/event/impl/DrawGround.java create mode 100644 src/org/rsbot/event/impl/DrawInventory.java create mode 100644 src/org/rsbot/event/impl/DrawItems.java create mode 100644 src/org/rsbot/event/impl/DrawModel.java create mode 100644 src/org/rsbot/event/impl/DrawMouse.java create mode 100644 src/org/rsbot/event/impl/DrawNPCs.java create mode 100644 src/org/rsbot/event/impl/DrawObjects.java create mode 100644 src/org/rsbot/event/impl/DrawPlayers.java create mode 100644 src/org/rsbot/event/impl/DrawSettings.java create mode 100644 src/org/rsbot/event/impl/DrawWeb.java create mode 100644 src/org/rsbot/event/impl/MessageLogger.java create mode 100644 src/org/rsbot/event/impl/TAnimation.java create mode 100644 src/org/rsbot/event/impl/TCamera.java create mode 100644 src/org/rsbot/event/impl/TFPS.java create mode 100644 src/org/rsbot/event/impl/TFloorHeight.java create mode 100644 src/org/rsbot/event/impl/TLoginIndex.java create mode 100644 src/org/rsbot/event/impl/TMenu.java create mode 100644 src/org/rsbot/event/impl/TMenuActions.java create mode 100644 src/org/rsbot/event/impl/TMousePosition.java create mode 100644 src/org/rsbot/event/impl/TPlayerPosition.java create mode 100644 src/org/rsbot/event/impl/TTab.java create mode 100644 src/org/rsbot/event/impl/TUserInputAllowed.java create mode 100644 src/org/rsbot/event/impl/TWebStatus.java create mode 100644 src/org/rsbot/event/listeners/CharacterMovedListener.java create mode 100644 src/org/rsbot/event/listeners/MessageListener.java create mode 100644 src/org/rsbot/event/listeners/PaintListener.java create mode 100644 src/org/rsbot/event/listeners/TextPaintListener.java create mode 100644 src/org/rsbot/gui/AccountManager.java create mode 100644 src/org/rsbot/gui/BotGUI.java create mode 100644 src/org/rsbot/gui/BotHome.java create mode 100644 src/org/rsbot/gui/BotMenuBar.java create mode 100644 src/org/rsbot/gui/BotPanel.java create mode 100644 src/org/rsbot/gui/BotToolBar.java create mode 100644 src/org/rsbot/gui/ScriptSelector.java create mode 100644 src/org/rsbot/gui/SettingsManager.java create mode 100644 src/org/rsbot/gui/SplashAd.java create mode 100644 src/org/rsbot/gui/component/JComboCheckBox.java create mode 100644 src/org/rsbot/gui/component/LogTextArea.java create mode 100644 src/org/rsbot/gui/component/Messages.java create mode 100644 src/org/rsbot/loader/ClientLoader.java create mode 100644 src/org/rsbot/loader/VersionVisitor.java create mode 100644 src/org/rsbot/loader/asm/AnnotationVisitor.java create mode 100644 src/org/rsbot/loader/asm/AnnotationWriter.java create mode 100644 src/org/rsbot/loader/asm/Attribute.java create mode 100644 src/org/rsbot/loader/asm/ByteVector.java create mode 100644 src/org/rsbot/loader/asm/ClassAdapter.java create mode 100644 src/org/rsbot/loader/asm/ClassReader.java create mode 100644 src/org/rsbot/loader/asm/ClassVisitor.java create mode 100644 src/org/rsbot/loader/asm/ClassWriter.java create mode 100644 src/org/rsbot/loader/asm/Edge.java create mode 100644 src/org/rsbot/loader/asm/FieldVisitor.java create mode 100644 src/org/rsbot/loader/asm/FieldWriter.java create mode 100644 src/org/rsbot/loader/asm/Frame.java create mode 100644 src/org/rsbot/loader/asm/Handler.java create mode 100644 src/org/rsbot/loader/asm/Item.java create mode 100644 src/org/rsbot/loader/asm/Label.java create mode 100644 src/org/rsbot/loader/asm/MethodAdapter.java create mode 100644 src/org/rsbot/loader/asm/MethodVisitor.java create mode 100644 src/org/rsbot/loader/asm/MethodWriter.java create mode 100644 src/org/rsbot/loader/asm/Opcodes.java create mode 100644 src/org/rsbot/loader/asm/Type.java create mode 100644 src/org/rsbot/loader/script/Buffer.java create mode 100644 src/org/rsbot/loader/script/CodeReader.java create mode 100644 src/org/rsbot/loader/script/ModScript.java create mode 100644 src/org/rsbot/loader/script/ParseException.java create mode 100644 src/org/rsbot/loader/script/adapter/AddFieldAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/AddGetterAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/AddInterfaceAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/AddMethodAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/InsertCodeAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/OverrideClassAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/SetSignatureAdapter.java create mode 100644 src/org/rsbot/loader/script/adapter/SetSuperAdapter.java create mode 100644 src/org/rsbot/log/LogFormatter.java create mode 100644 src/org/rsbot/log/LogOutputStream.java create mode 100644 src/org/rsbot/log/SystemConsoleHandler.java create mode 100644 src/org/rsbot/log/TextAreaLogHandler.java create mode 100644 src/org/rsbot/script/AccountStore.java create mode 100644 src/org/rsbot/script/BackgroundScript.java create mode 100644 src/org/rsbot/script/Random.java create mode 100644 src/org/rsbot/script/Script.java create mode 100644 src/org/rsbot/script/ScriptManifest.java create mode 100644 src/org/rsbot/script/background/BankMonitor.java create mode 100644 src/org/rsbot/script/background/WebData.java create mode 100644 src/org/rsbot/script/background/WebLoader.java create mode 100644 src/org/rsbot/script/internal/BackgroundScriptHandler.java create mode 100644 src/org/rsbot/script/internal/BreakHandler.java create mode 100644 src/org/rsbot/script/internal/InputManager.java create mode 100644 src/org/rsbot/script/internal/MouseHandler.java create mode 100644 src/org/rsbot/script/internal/ScriptHandler.java create mode 100644 src/org/rsbot/script/internal/event/ScriptListener.java create mode 100644 src/org/rsbot/script/internal/reflection/Hook.java create mode 100644 src/org/rsbot/script/internal/reflection/Hooks.java create mode 100644 src/org/rsbot/script/internal/reflection/Reflection.java create mode 100644 src/org/rsbot/script/internal/wrappers/Deque.java create mode 100644 src/org/rsbot/script/internal/wrappers/HashTable.java create mode 100644 src/org/rsbot/script/internal/wrappers/Queue.java create mode 100644 src/org/rsbot/script/internal/wrappers/StatusQueue.java create mode 100644 src/org/rsbot/script/internal/wrappers/TileData.java create mode 100644 src/org/rsbot/script/internal/wrappers/WebAction.java create mode 100644 src/org/rsbot/script/methods/Account.java create mode 100644 src/org/rsbot/script/methods/Bank.java create mode 100644 src/org/rsbot/script/methods/Calculations.java create mode 100644 src/org/rsbot/script/methods/Camera.java create mode 100644 src/org/rsbot/script/methods/ClanChat.java create mode 100644 src/org/rsbot/script/methods/Combat.java create mode 100644 src/org/rsbot/script/methods/Environment.java create mode 100644 src/org/rsbot/script/methods/Equipment.java create mode 100644 src/org/rsbot/script/methods/FriendChat.java create mode 100644 src/org/rsbot/script/methods/Game.java create mode 100644 src/org/rsbot/script/methods/GameGUI.java create mode 100644 src/org/rsbot/script/methods/GrandExchange.java create mode 100644 src/org/rsbot/script/methods/GroundItems.java create mode 100644 src/org/rsbot/script/methods/Hiscores.java create mode 100644 src/org/rsbot/script/methods/Interfaces.java create mode 100644 src/org/rsbot/script/methods/Inventory.java create mode 100644 src/org/rsbot/script/methods/Keyboard.java create mode 100644 src/org/rsbot/script/methods/Lobby.java create mode 100644 src/org/rsbot/script/methods/Magic.java create mode 100644 src/org/rsbot/script/methods/Menu.java create mode 100644 src/org/rsbot/script/methods/MethodContext.java create mode 100644 src/org/rsbot/script/methods/MethodProvider.java create mode 100644 src/org/rsbot/script/methods/Methods.java create mode 100644 src/org/rsbot/script/methods/Mouse.java create mode 100644 src/org/rsbot/script/methods/NPCs.java create mode 100644 src/org/rsbot/script/methods/Nodes.java create mode 100644 src/org/rsbot/script/methods/Objects.java create mode 100644 src/org/rsbot/script/methods/Players.java create mode 100644 src/org/rsbot/script/methods/Prayer.java create mode 100644 src/org/rsbot/script/methods/Quests.java create mode 100644 src/org/rsbot/script/methods/Settings.java create mode 100644 src/org/rsbot/script/methods/Skills.java create mode 100644 src/org/rsbot/script/methods/Store.java create mode 100644 src/org/rsbot/script/methods/Summoning.java create mode 100644 src/org/rsbot/script/methods/Tiles.java create mode 100644 src/org/rsbot/script/methods/Trade.java create mode 100644 src/org/rsbot/script/methods/Walking.java create mode 100644 src/org/rsbot/script/methods/Web.java create mode 100644 src/org/rsbot/script/provider/FileScriptSource.java create mode 100644 src/org/rsbot/script/provider/ScriptClassLoader.java create mode 100644 src/org/rsbot/script/provider/ScriptDefinition.java create mode 100644 src/org/rsbot/script/provider/ScriptDeliveryNetwork.java create mode 100644 src/org/rsbot/script/provider/ScriptDownloader.java create mode 100644 src/org/rsbot/script/provider/ScriptList.java create mode 100644 src/org/rsbot/script/provider/ScriptSource.java create mode 100644 src/org/rsbot/script/randoms/BankPins.java create mode 100644 src/org/rsbot/script/randoms/BeehiveSolver.java create mode 100644 src/org/rsbot/script/randoms/CapnArnav.java create mode 100644 src/org/rsbot/script/randoms/Certer.java create mode 100644 src/org/rsbot/script/randoms/CloseAllInterface.java create mode 100644 src/org/rsbot/script/randoms/DrillDemon.java create mode 100644 src/org/rsbot/script/randoms/Exam.java create mode 100644 src/org/rsbot/script/randoms/FirstTimeDeath.java create mode 100644 src/org/rsbot/script/randoms/FreakyForester.java create mode 100644 src/org/rsbot/script/randoms/FrogCave.java create mode 100644 src/org/rsbot/script/randoms/GraveDigger.java create mode 100644 src/org/rsbot/script/randoms/ImprovedRewardsBox.java create mode 100644 src/org/rsbot/script/randoms/LeaveSafeArea.java create mode 100644 src/org/rsbot/script/randoms/LoginBot.java create mode 100644 src/org/rsbot/script/randoms/LostAndFound.java create mode 100644 src/org/rsbot/script/randoms/Maze.java create mode 100644 src/org/rsbot/script/randoms/Mime.java create mode 100644 src/org/rsbot/script/randoms/Molly.java create mode 100644 src/org/rsbot/script/randoms/Pillory.java create mode 100644 src/org/rsbot/script/randoms/Pinball.java create mode 100644 src/org/rsbot/script/randoms/Prison.java create mode 100644 src/org/rsbot/script/randoms/QuizSolver.java create mode 100644 src/org/rsbot/script/randoms/SandwhichLady.java create mode 100644 src/org/rsbot/script/randoms/ScapeRuneIsland.java create mode 100644 src/org/rsbot/script/randoms/SystemUpdate.java create mode 100644 src/org/rsbot/script/randoms/TeleotherCloser.java create mode 100644 src/org/rsbot/script/util/BankCache.java create mode 100644 src/org/rsbot/script/util/Filter.java create mode 100644 src/org/rsbot/script/util/SkillData.java create mode 100644 src/org/rsbot/script/util/Timer.java create mode 100644 src/org/rsbot/script/util/WindowUtil.java create mode 100644 src/org/rsbot/script/web/PlaneHandler.java create mode 100644 src/org/rsbot/script/web/PlaneTraverse.java create mode 100644 src/org/rsbot/script/web/Prerequisites.java create mode 100644 src/org/rsbot/script/web/Route.java create mode 100644 src/org/rsbot/script/web/RouteStep.java create mode 100644 src/org/rsbot/script/web/Teleport.java create mode 100644 src/org/rsbot/script/web/Transportation.java create mode 100644 src/org/rsbot/script/web/TransportationHandler.java create mode 100644 src/org/rsbot/script/web/methods/Equipment.java create mode 100644 src/org/rsbot/script/web/methods/TeleportItem.java create mode 100644 src/org/rsbot/script/web/methods/TeleportJewelry.java create mode 100644 src/org/rsbot/script/web/methods/TeleportNPC.java create mode 100644 src/org/rsbot/script/web/methods/TeleportObject.java create mode 100644 src/org/rsbot/script/web/methods/TeleportRunes.java create mode 100644 src/org/rsbot/script/wrappers/RSAnimableModel.java create mode 100644 src/org/rsbot/script/wrappers/RSArea.java create mode 100644 src/org/rsbot/script/wrappers/RSCharacter.java create mode 100644 src/org/rsbot/script/wrappers/RSCharacterModel.java create mode 100644 src/org/rsbot/script/wrappers/RSComponent.java create mode 100644 src/org/rsbot/script/wrappers/RSGroundItem.java create mode 100644 src/org/rsbot/script/wrappers/RSInterface.java create mode 100644 src/org/rsbot/script/wrappers/RSItem.java create mode 100644 src/org/rsbot/script/wrappers/RSItemDef.java create mode 100644 src/org/rsbot/script/wrappers/RSLocalPath.java create mode 100644 src/org/rsbot/script/wrappers/RSModel.java create mode 100644 src/org/rsbot/script/wrappers/RSNPC.java create mode 100644 src/org/rsbot/script/wrappers/RSObject.java create mode 100644 src/org/rsbot/script/wrappers/RSObjectDef.java create mode 100644 src/org/rsbot/script/wrappers/RSObjectModel.java create mode 100644 src/org/rsbot/script/wrappers/RSPath.java create mode 100644 src/org/rsbot/script/wrappers/RSPlayer.java create mode 100644 src/org/rsbot/script/wrappers/RSStaticModel.java create mode 100644 src/org/rsbot/script/wrappers/RSTile.java create mode 100644 src/org/rsbot/script/wrappers/RSTilePath.java create mode 100644 src/org/rsbot/script/wrappers/RSVerifiable.java create mode 100644 src/org/rsbot/script/wrappers/RSWeb.java create mode 100644 src/org/rsbot/script/wrappers/RSWebTile.java create mode 100644 src/org/rsbot/security/RestrictedSecurityManager.java create mode 100644 src/org/rsbot/service/DRM.java create mode 100644 src/org/rsbot/service/Monitoring.java create mode 100644 src/org/rsbot/service/ServiceException.java create mode 100644 src/org/rsbot/service/TwitterUpdates.java create mode 100644 src/org/rsbot/service/WebQueue.java create mode 100644 src/org/rsbot/util/ApplicationException.java create mode 100644 src/org/rsbot/util/Base64.java create mode 100644 src/org/rsbot/util/StringUtil.java create mode 100644 src/org/rsbot/util/UpdateChecker.java create mode 100644 src/org/rsbot/util/io/HttpClient.java create mode 100644 src/org/rsbot/util/io/IOHelper.java create mode 100644 src/org/rsbot/util/io/IniParser.java create mode 100644 src/org/rsbot/util/io/JavaCompiler.java create mode 100644 src/org/rsbot/util/io/PreferenceData.java create mode 100644 src/org/rsbot/util/io/ScreenshotUtil.java create mode 100644 src/org/rsbot/util/io/UIDData.java diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..4bf188e --- /dev/null +++ b/license.txt @@ -0,0 +1 @@ +dundundun \ No newline at end of file diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..f6bdb90 --- /dev/null +++ b/make.bat @@ -0,0 +1,90 @@ +@ECHO OFF + +SETLOCAL +CALL :setvars +SET cmd=%1 +IF "%cmd%"=="" SET cmd=all +CALL :%cmd% +GOTO :eof + +:setvars +SET name=RSBot +SET cc=javac +SET cflags=-g:none -Xlint:deprecation +SET src=src +SET lib=lib +SET res=resources +SET out=bin +SET dist=%name%.jar +SET lstf=temp.txt +SET imgdir=%res%\images +SET manifest=%res%\Manifest.txt +SET versionfile=%res%\version.txt +FOR /F %%G IN (%versionfile%) DO SET version=%%G +SET scripts=scripts +CALL "%res%\FindJDK.bat" +GOTO :eof + +:all +CALL :clean 2>NUL +ECHO Compiling bot +CALL :Bot +ECHO Compiling scripts +CALL :Scripts 2>NUL +ECHO Packing JAR +CALL :pack +CALL :end +GOTO :eof + +:Bot +IF EXIST "%lstf%" DEL /F /Q "%lstf%" +FOR /F "usebackq tokens=*" %%G IN (`DIR /B /S "%src%\*.java"`) DO CALL :append "%%G" +IF EXIST "%out%" RMDIR /S /Q "%out%" > NUL +MKDIR "%out%" +"%cc%" %cflags% -d "%out%" "@%lstf%" 2>NUL +DEL /F /Q "%lstf%" +GOTO :eof + +:Scripts +CALL :mostlyclean +IF EXIST "%scripts%" "%cc%" %cflags% -cp "%out%" %scripts%\*.java +GOTO :eof + +:pack +IF EXIST "%dist%" DEL /F /Q "%dist%" +IF EXIST "%lstf%" DEL /F /Q "%lstf%" +COPY "%manifest%" "%lstf%" > NUL +ECHO Specification-Version: "%version%" >> "%lstf%" +ECHO Implementation-Version: "%version%" >> "%lstf%" +jar cfm "%dist%" "%lstf%" -C "%out%" . %versionfile% %imgdir%\* %res%\*.bat %res%\*.sh +DEL /F /Q "%lstf%" +GOTO :eof + +:end +CALL :clean 2>NUL +ECHO Compilation successful. +GOTO :eof + +:append +SET gx=%1 +SET gx=%gx:\=\\% +ECHO %gx% >> %lstf% +GOTO :eof + +:mostlyclean +IF EXIST "%scripts%" ECHO. > "%scripts%\.class" +IF EXIST "%scripts%" DEL /F /Q %scripts%\*.class +GOTO :eof + +:clean +CALL :mostlyclean +RMDIR /S /Q "%out%" 2>NUL +GOTO :eof + +:remove +IF EXIST "%APPDATA%\%name%_Accounts.ini" DEL "%APPDATA%\%name%_Accounts.ini" +IF EXIST "%APPDATA%\%name% Accounts.ini" DEL "%APPDATA%\%name% Accounts.ini" +IF EXIST "%RSBOT_HOME%" RMDIR /S /Q "%RSBOT_HOME%" +FOR /F "tokens=3" %%G IN ('REG QUERY "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "Personal"') DO (SET docs=%%G) +IF EXIST "%docs%\%name%" RMDIR /S /Q "%docs%\%name%" +GOTO :eof diff --git a/readme b/readme new file mode 100644 index 0000000..21a5b03 --- /dev/null +++ b/readme @@ -0,0 +1 @@ +Adding stuffs here soon \ No newline at end of file diff --git a/resources/Compile-Scripts.bat b/resources/Compile-Scripts.bat new file mode 100644 index 0000000..930d9bd --- /dev/null +++ b/resources/Compile-Scripts.bat @@ -0,0 +1,32 @@ +@ECHO OFF + +TITLE RSBot Scripts + +SET cc=javac +SET cflags= +SET scripts=Scripts\Sources +SET scriptspre=Scripts\Precompiled +SET jarpathfile=Settings\path.txt + +IF NOT EXIST "%jarpathfile%" ( + ECHO Path file does not exist. Please run RSBot and try again. + GOTO end +) + +FOR /F "delims=" %%G IN (%jarpathfile%) DO SET jarpath=%%G + +CALL FindJDK.bat + +IF NOT EXIST %scripts%\*.java ( + ECHO No .java script source files found. + GOTO end +) + +ECHO Compiling scripts +ECHO. > "%scripts%\.class" +DEL /F /Q "%scripts%\*.class" > NUL +"%cc%" %cflags% -cp "%jarpath%" %scripts%\*.java + +:end +PAUSE +EXIT diff --git a/resources/FindJDK.bat b/resources/FindJDK.bat new file mode 100644 index 0000000..369cd26 --- /dev/null +++ b/resources/FindJDK.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +ECHO Looking for JDK + +SET KEY_NAME=HKLM\SOFTWARE\JavaSoft\Java Development Kit +FOR /F "tokens=3" %%A IN ('REG QUERY "%KEY_NAME%" /v CurrentVersion 2^>NUL') DO SET jdkv=%%A +SET jdk= + +IF DEFINED jdkv ( + FOR /F "skip=2 tokens=2*" %%A IN ('REG QUERY "%KEY_NAME%\%jdkv%" /v JavaHome 2^>NUL') DO SET jdk=%%B +) ELSE ( + FOR /F "tokens=*" %%G IN ('DIR /B "%ProgramFiles%\Java\jdk*"') DO SET jdk=%%G +) + +SET jdk=%jdk%\bin +SET javac="%jdk%\javac.exe" + +IF NOT EXIST %javac% ( + javac -version 2>NUL + IF "%ERRORLEVEL%" NEQ "0" GOTO :notfound +) ELSE ( + GOTO :setpath +) +GOTO :eof + +:notfound +ECHO JDK is not installed, please download and install it from: +ECHO http://java.sun.com/javase/downloads +ECHO. +PAUSE +EXIT + +:setpath +SET PATH=%jdk%;%PATH% +GOTO :eof diff --git a/resources/Manifest.txt b/resources/Manifest.txt new file mode 100644 index 0000000..6147632 --- /dev/null +++ b/resources/Manifest.txt @@ -0,0 +1,9 @@ +Main-Class: org.rsbot.Boot +Class-Path: lib/xel.jar + +Name: org/rsbot +Sealed: true +Specification-Title: "RSBot" +Specification-Vendor: "org.rsbot" +Implementation-Title: "RSBot" +Implementation-Vendor: "org.rsbot" diff --git a/resources/compile-scripts.sh b/resources/compile-scripts.sh new file mode 100644 index 0000000..7503930 --- /dev/null +++ b/resources/compile-scripts.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +cc=javac +cflags= +scripts=Scripts/Sources +scriptspre=Scripts/Precompiled +jarpathfile=Settings/path.txt + +cd ~/RSBot + +if [ ! -e "$jarpathfile" ]; then +echo "Path file does not exist. Please run RSBot and try again." +exit +fi + +for file in $scripts/*.java +do +if [ ! -e "${file}" ]; then +echo "No .java script source files found." + exit +fi +done + +echo "Compiling scripts" +for file in $scripts/*.class +do +if [ -e "${file}" ]; then +rm -R $scripts/*.class + break +fi +done + +"$cc" $cflags -cp "/$(cat $jarpathfile)" $scripts/*.java \ No newline at end of file diff --git a/resources/lib/xel.jar b/resources/lib/xel.jar new file mode 100644 index 0000000000000000000000000000000000000000..8048cc1527081e5e9bfdc0f709badb6610df5d3f GIT binary patch literal 293671 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

Inf^ZiE0Uu|s&#A^7B zt9H#^TfVz@Z$bIS^3wWRif6vp_8%u6Srql}D|bn;(>#I=>P82TJhu#F6(m|#V8Nwa z^rQM_ru*Xdafz7nM1Sd;Dev`vIZ;PO`RtKtOELVA7UC!L^uuFvyFDFkF~Vzdpqgz0 z%sacpvO!~xV!JbK!UNBKewN6Wyqa;BC>ta3+;(SfuM52JXMU~|=R8KHHj&CjrYM4z z2gi+q=k;$Uu}2S+Fu0U*>-Ra)xY8;t8ixC(AeBTC_<05u1?P8Neq_fI^{)zwIv}Mw zSPhy7EEay+HvS&(ru^5pULeWO6ndxzWVUT=B26s;L#@hMuyp0=8}(f;oF4Af-X%pvX57-j+sKHh;jm0UeKLLQ7FSU~C@#u|8*`f6>Sw3Rnx z_;Yo$YfpK=HY7V^y4H4yx}DiBrtc5MMYE(5erM)&>z~lWx&dSTH8UoDS=x2q2f-DM z2V=xNk$r(TW{IFDt5aX+PR}o~!q+dUx0~-XWj^TdmNXSiLQM(XbpH2fy6k6j-}t1DFQ@a8&5tyflwUrCn7rK5Qc@?mF%V~SZn0TkTe=mOYJJ@>=xqb&N8>WEPemd4b4F#ne_UlAf zR?^N~T0pjLOusBu;;_f9t|7QL5&P!yG!# z=-syX1F39=eggV7)vK@ZF^N8jy9HzfB%VKZO~{5x84`8L+jO0EuZTft@fb4IiJhc2 zWt-H82IOtp&QZJCpkb-%1YHI$qBfx`XAli(O=73?)n&ZG2)ELORJ=m6Px!8i*`xdz zx84O-yvEprg*3m+)l&RaoKN-cHE9mRBRB{{x=ZqEFrFdKr)+nO)PVjG7NjBFC4Ds) zUlZ3Ue$`aJx*(-b^XOH!LFk#ai$hkE(kXN$4$2_iAbm6kWsq$UKdOUTq`LH)575)z z@#4=DpY-Beq`P#Sm9N-A_mW*2&IP;Vpo#eN1fTBR(|Cf|$Fta%+}#jT0ouo}TI&*f z9v-q+JMlTOohrfJ%Ej-AyCq}-B#&C4DJft5E4X-r#FzNp6f-XUxR>(X1=4Q%>n5@m z$!DDSjl{!FvPV16xumbg)jMf7#p8Dnf#jRwl|;Ni@=MsR0+}EAqf6R5#`Js9ZVjm) zooCAKT6}lhOV#eV>9a4x<7be7^qW-hv(^UsfElJ>B+cNap%y{hzR>$BhV>nOUYf|Y5QhqBt}vuO3!U)#+82jv(&myz3zfj6P0H5c`G^E-}G$n$$s>9 zE%CU6YQbGM2lhkPgs&t)&mhMF(lJ+jjxzr}?wDuQUK6sI1lk49)dJY$V_tg|NQJ~l z*?3;9D^ySrbEfWr(;&5G^>wQWnosDi2pNXV2hy1y4(pk7N~@WkF*C4i%f|{@wzt@tCZDHo`A%}o zSQ{sXt<@N}hpi*F$WA*TOq^9@u6F#5JNz5B=k#Gbeb!{Y^4Pew^e|=*IBkYY$7v-^ z%u*D^^mmFeywyYGIPH#rtO!y3hlvR&-A<$P~RGOJs!OrC4_ zspZcH%HE3HuVJz=yT-WOM|9+81i@yT;yFvOHpCUJX!e>XPtfuHd@V*?20i7D1f(yP z#`orz{`Q%CHW*t5dQZ5E4?CG>$=>;7)B2X5Cz9gObs(GBn10@k-r|$~38FKJxIQ=8 zfo~3;IEaQoxMMtKT8m2H$^MK#JMzWa6h)O)E?e@7RK6_%v{n-*m*<%A-j)VCKUB$q zQg6898DZ)~Z)qN6qj^A@F)G7^pocMjAjj`$!@{Gv7~ymGL&uB^CzD}-Gqwd+VeC(W zX-Mwrm!f7K(Z)5Rw!zNJFLB=P9lsT(WEimtu zt5;+1m9I@k(@a#ckl1BtzOpA1ItY5zX`8%M zX9rn}{c=e-(QSy!D(TB&IPw)DoR*(`6|T7_9>zgmk5Frrna#ySHLDSOz-AVRCpg4} z3Fq%*HTx_v+7RTG7T$sRE-~8_XCc|K8udu{O7f@^U*ov9b`!JvMru&jg*}7HRZaG+ zW_6-rL3_Z4HTMBFdbdrK-ejZrXh&0p`YFY{HZzMg8g$77TB8Eqs!v%X=gCM8Bbu?U z)lYVbr{VSYLCayYI4tGIdbhKb9ps~D5<~YA?aD(qx{rsHD_CX|#8>X9o;z;MWwWEr zt>?vdC;O+iE>!P54ogG!K{Ke6E4s(0)h-doog}xJXrQlJtB2(qp|CI1u)icm-P^7* zlEGglg$+u7%+0#(0xzLKT!E9p`f z3QnFNvsD~*HXq+9HgFDCi0qn?UBth(n%VhCLeO8E80*LV9c)^7 zRL1ml&m&Qy$itR;9*J}-d~|J9@KlU^S3`{spA93-#&wS(eb1Hf>sn=Z2?LzDS}U<3 zOklUJq|WvOcx3pZLLw>Zhvk(x--!1HMArRhfAYEGCTaCYSd9^=IHtwM+piec+3#UZ zdCP~C6+J{K1i?<3r-Rf%0c-~=j5B#AbrV&6?7YUuE@acZCH$kfQxT|+%C@eNQK@Q7 zxyt=>BZnEyxvj=J?GY7P$u5qbEv=^KTlY>A5p*$AbTg!ex8t)jo=u8bku`-yOk;x& z7|!zE@g}>?SyIFcOAGpnr#@trk{*g^p822`A6!o>TlF=Pf*U4~M21-YIu$VI#dL8I z?~N@Kg4K+BGVvm$W+lf8G1)&hnr7uS!WnnBq%!>7GgdHpLy9Pa<%k4#E*rFLpn>;d z$5o%6V`Y?}%P@-!U>HnUfT^FyC0kqHm~f7|&WL;b{`mFIYXKML#mp*i3S1u)eR&o_ z5cVo|{W*1pXlGa`Bvh6D_k^TG9RGZ${!?O@&0~$@`V6%6w2pe(HhrmnJiC0pyVNeZ;D|z8 zrc_2{FDna1$2VtWC1m-TuX#x=?)x`);Cn4{5Z8!p2#1oF8nL)(te5dKmV)|7Eb3yC zQcz8mq$llDh92Ny`sw$ev}R-_sneJtJ&KH3lm=6@>VYsB6qP1+i`v=rVtY_j9V#rS zv0qLTP_3=cMwoVOo58+ZhRp?#SbA+e z0DtW;9VJWRuGv30hdY-RVPP$C*L3@)o6KV9U0?$=j#&O}{qUr598d%q`(WZrzu;mQ z+e7vuYK{M-T|&powKE)nTj?-2k#h>BZiS2$I!IOC9x#jW775yNH4GAVifdKoUm|2E zY#LYAJpkdFHO%O$`eKq^A*jCsSC8Mm<;YPu*OxZ5T(XVdmbr4>pbMUT+|zHDjb1N- z%co0kpB$gm<0b%XeOc)|b2qFFy-(oGjW@3gQDvlJY~u*e?9%lCs$J^Vbk?L;reT~X zLr1G@I0KV%ZFfT$RqHEbNNg( zDY9|O#wBiOuiG2jn!Q4Pduyh1fof}TevWjDTH36FE7M3T3k9I@ZJ++_O*ti6b;j@I zo+cpWZ#dPBNNmLWKFz2}xgbkT^2-4f>wtD78;uHiBtBlGGoWG7UWGS`BFdr^ue5K8lP)j}t!wQK4s4QJEjXlhO&_@9wqwAl-t^DyL*fa!-nT98 zRBS13xh+VvC}sCw0XyvfEM$hXtW@zbOAn0Vl(!F#V!(eF@T#J1;;j_dXb)QY@?29W3$%Pl?| z<;7H7`-F76IK!=QfJSO5%N?w>_F6C&Q1i-wu@84NR$O_D8t?|X%d)oQeV%L733s4G z#JoqFl7PBc@(YGU1)GL*4of-5 z(6!j=e>jrUr&XnT$FqE64+_%17v zPxC@IF9u9D^bkgwcW@UfD)0t-1W9o;;UnS^OTH1G6{9Du{U><_`!ews(n~c_q>~(T z+OkGoukq{B^ooe?Z4eI9Iv)`A4CpR+Be(~9RE|-bL_e@XV@gQVE*ke>qe^{Ip!%MC z58);6E%$tq9J5X}^rAzRfGl+-I0w5Ys?;{&B=RH`y~95RyJz$fIf^`ydv6J-eLKng(!%?4zP?d&OTR#5lHj7IaM)`D%ZLFTJUqbD=fy zv!|GGV(q^eI#$v{o2USJe|DDJY>0um<(f1-ok-%Tw z3%@K?7kXHbHx&$JrJvJ&TT&!R8!Od`!O-lw?6M)bhIM!$CbG;WF7h^Gm36tFpoNnn z=Jm*dtD*_x#|#Kb>(9H0!1^zj0Pfc8i_uVBg%f! z8A@f@unT~c)XacNj?iu6O4kqmhM=(gj*2*Y6w_ob%QL{8 zD7C(j$Nu}2zCpIyQb8lmQ)~Go{3@}p<~^sjf#nq)@{uVT)N31I~^#J z<}XzDJ0EX*coX5q8eND^S-ioAA5xbPbbrX-NDY^|z8<;JAqx<{rv5E72a_$r=)R2D zTE&#Kq^=YsGuW8mr3y;7$EE3T{Ys)*7^xDkRtzj9NLDMBS!22bk&#b33yUsOkL)E) z-OQ3EU^=*NxtpR658nc7X{{v&;843?N30T@dMrO7q~JB@*CdpC)yUXw1I;9gdU?FH(DOu6?RWwdqmGG2QNRvT;ZE zYf^e&`k*pkC+wDPFOf!To3?(ZWwLdrMT==2dine%%_*~;Qv7S+mFy&@ z(np*<9eREA*%Ss9W9W*0x%Rv)xLWm+`-%=k-G5bs9p~McDgT8R7iR&kMVK`nst*gd#cN zSuC_9qpQ=H@DcLp#?bP$(&^&p7mD_V-~7<`A3r`vRoNhz*kc}=Px*eq08ktDMy6as z6_Z3G6n8?H`jTT5YA6ODIvg?_x-AAPhfJXfF$I^i6RL)m`ArJrO#*AwmXk=X1|7f~ zdT5-Z93n@-8+quI!xo~3o(XWmnpzS&K*^MG5}XQ@+lM4us5Ot%726MAV=XtzCT|TG z#Z;m3i0~jE!ivvY&ui+8d%b<^eUllKw^dl}OnAM;=zCM$EySid*O&0|NZEe3xSn~z zcvy(b0;e=}2EE=g-@zc3A?6?w18ZaeZ4rkKIdmZv8004drQVpSm6R<(=H=)x*0pAF zy7K#$q4OxZl1>m)ygB%pDd>w(^CUy_j>@@Li>B;*9&k0n$kSL91xmKEMxD4j$zb`x zdc|f=8tiq&rW1P)X?vX-DBbu|7YduHzPE*)WXlVM?M#*4){q9YHDH3Qa)&kObgX3c zKZaa$-)25FGYRm&;lr*w`baDLTwkXv!rAI^>+BCAmm3aTVok^6Oty ztbIm3_jPN)J+ni;E8?DqN;8q8&WDz~O4Pkw_I*R3AF8x%$<5C~J}Gj#Mp2U!FhXh4 z4A638AX;bv`89*Z<4$2yS4`*CJOSAy{D51GsTr{e6akrAf~gxZCX{xmTePVWu}YM7 z>D#oD^OIo{I`wdecHyoKat|3|%6trSba#_u7A zMmh4+c~`}j`V+hqO9vRQY{xI-tABRFI{rpr2k_PO^R1=(w?xU;Fx|?V*Bz!a{6!0s@#F@+l?!Kj}&873jw!3Zl!1vi~q1h1%`V^gBEmRK6>aHmqC2ZCGR zi}XgWxd0>_AaD)*D>oP;RJra{E83|;eZ)M`&qUc{sD*$eL9yQ4ioz$LUYzU|kFXRP zp{m%Ybxa41X&f;{XmmspJn1?nMc?BDo7F#!ypjEKQ-p6%HKYXW`E@dQ5Z8OWhL?mE zzLIR5)kFXzp~M^NyJ~UmwMiwP+)$*+V5yBfmY~<25F~Ou8Tb2G#!mjA71a+WAFrZ( zCbmxv?W-|k7541SND5+L1>W^{?RTbRHJR(WB z&GtBQ=OdDRC-sgyNTe;0zEeuha+>$mT_i?9%j59)I>PDnIKS!%puPxT5bqw@9B3L}P(Mn_a(z&lK3`An4K`R8b!w=>DE9!lDr z=c70!f~1?mP6yC4zdPmFW@L()Y#Tj@#a_^IZN#s73G8ed&zTldFBA_ur#WQGX=Y{z zZGT>T!7+>X<(NtpGegZ3b*d2baGx3{`}-t@jiP&4IwWp^*AzmvD@*t}Teqw(+`Tz3 zs5dyQQ!tU8(cKPWd^gz%=|`LoCrC$*Q5I|aLax0vl{njE#^Zg*V88H7tX*CY)V!XX zIOztc**TA2mdc-Ltv1?TJgicl2%=IA$R^E>mZ9$Rf)}Lp3LdMLwW=efo`Mtj z^a>`cmX4~U=k9+38wRVE`l_S(?gDN2bVfR>ma?j&n(l%Txc?S-!ls{7ShWOI-Nc`o z`hSaf3hG4o#m$xjFVRr5A+kU)-3H0^@X@Lf~M z8M<-qO*8PoF@5}iGjqb4H*3X7u<^*tX69+N;Q#f7; zYDMeO=aRdWfB)AL>K)oOy_xBNX~h z`ka19TvFF%oV4v_G4nKAa8nFCP!6cc&||Ghq$|?lsX6HVSluZC7Rsl~WpQQ-TJu^h zJ2liDS%GGjrVGgOfs)eca?zZbMAp0l%T69*zgmh%Tj|u1_l}P+im>~RfpySRVAe?f zLf_5hBAxVv@aKWUo!jWC1yET#b^d=A2jQ$nO0W3ON&mwtB<}L&HU;wfQST| z;iQi@ir_vX;mLx8q93<6!ZiGgOSsbh>PT95p#_aOJ$p}ctykKef{Ts?H{X- zE`PDpsy_A&*Q+!qaf2rDEdOI{TTCj^MPYFgGFb5{E;{8@9clcSss2-7^b0ufC0*__ zXXY0xUdTl!xGK<1v=X>X+9}K-vqYzmC!aslFjLrxbIKxzEp(ieH~3H|=O$#Fs^#aQ zNKSZ26a3Ml)!5>^l~+@wbF|a4flkXpvKD zQKd(yIFO${lzpE*vHbtgXNs9P8aaUMoIwAZJ{hTcq>iqR@fV0i837^nNjq`5iBp!^ zzStTw7A~v+5>kvU2gsWxK$y9j8cwOzxV6dPn7JA!YzGMwbud#CC+ALByb2 zRBA082151z&1lD;Mn~3h$=^50invCs({KRD8!}Ym6tKyV+gEH-W*i&8=y22&*;+4+ zxhj^S6tW2SP(%mTYq*AmWELGLbz|@=9c?4( z!jEte?@veb3(O$NZg3UpETdxBBA3sSt@kvn6momBg1MRH&$${0Q~ z%;gUcKDZ?sxW1Q{)vM%(7`>R&g1fb8k$#Kz2|GzI*6cEwq#5dPA@B9WYZ~xK7WDV# z87(FSKpg|s8`;$k(Y>0dYHWTIU8

Eo9dl~c*qFhQ5!jJA;2<=XwunEKmvuqU+XYpR3B zVsd>8j}rX|10v$BzicVWKy>%XhKf@Y6o2LV9-1UXWni7EmXXoA;Z*bdO@BLxwHFhV zq^wU*B9@=xCwd^7folmpM>*AhKT*Cn*@Mg>2ww&h$Q+VBu(9&gPy_cYvH}-*WpydM zvCYvt2?57m2JHG@t-}h{bU!UoXP1Q+s;P&*sh@yUgP_NsEbB&ncrReWc&HtCuJzZ` zef*qZbPhH3h@~6SYqHO5HL6FzF&M|RV!N_g85mdsQ00FuN8b9;m3nAV6+Tm@vX#R% zO`kXlaa$EExG=q`!;<~cmTLTT@mmrkCLnG5OY(^4TgmqvTmdmNY{-4L{tzm(qyf9TqvkQ3JPSBt0{g^(f2W}U#c;R# zgw_^zv3Yx=Mj}=ukZaUZ%PBW}f6^Tr`HnItU59^lir^7-tJjNk@syNPA7K;s7K`LK zd@G1&eEOinfwML2%KBipy=ArARL!~CBxtGV)N%Dv$z#Ai$J{3qit_s{Ee)h83Q3K# z9=!dFjuja{2n=QT!hFyYLI}Ue8y?@@csXd@8;niEgncV%Z5=5n4XwKz(j?Wg*qn#C zCN9#c)0*q~$`@F-vT)<2#E;*G3*(M@pNwCX&>24P`g-->Nap{P*_UxeP3AA3K1F^o zU;YPg0hJFEh<||_Q~~7w`tYAH&qyVK511#84`Y=_iTp%iT% zj5+R4k`<0-N;w_;SH*-Zr2;G6mTv|zI4H7GuhhMsaxv0WaxDiv9Si1<(hvlQ&?{6CnH++1g)yV(y}G$w*yQ(9lWw;M!+ zB3urqw24B$fxvvKcrcYhQaBpv!qFld$kF0r4Lu6?+q(9H%2|3R5JWHy<%nPM+Wi?N%@fu`x1`yG?s-<1st5@2D@T_)( z!dXZEdU6#_s&gC>Pb$Pf#L*7oIu@`v762Ue(y9e^_A)S+>6+kzEc1w_9W$G@ygoY~ zKf*%*`DzkEFZb)QrYR z5^xGD1j}}RI}CyStqCKxWQF4tN`P{Pi%z63py}tHe$93-u6H~N?{TGMz<^!7jjjSjAOnB#e74`dTZ2y;{$G7J%^L=O8=3kb3dDGWq==E4s z6wuEdufy+D5ZK6TtkRd9)j7c+y?$fxB})M_H5o{Q(E!qy4)>7)VQ?~J2Y_8>xPXJE zf~$v}F1u;K;SVn*ir`^SDz}{&KSaVESX~^UJLoC*V{^x321nM+4%HtQ!IZTrrHcZA zks|p=NEbcv{Ex-(dX0mwrM#~a#(2M>iAnB}`iG>hrnP)zLd5p=n; zAl}0Q)Q2Yh?{+~}KdQcW2eMlAh|6}Q(JWkUOW>wgrB-7>h2z%o;un6li&!@+_6KgYqmApKMgz6%`2VPktA>w#-)}ejaDq6 zdAInN1jP-{*jUv>?VB-#AH{+@j9tPTIN~CyTq%``%e7eNi%&uaweYx7%jyCv+2*E_ zHRFMW4RLtr0eH*zR(XJtUcRrHi(hm><&Avnv6|`gaj5}Gb3=@)0Sc%SWx#wipq3U4 zh4Oalyqk#axm-SL3y`7%t6lj-#+V_@QFdSz_eV_jY+g+EN|cnTC;t*=a|6%ZOuF>(kQGE_1vAf>z+f#l)(uyKSq-SXXLU`3?7`y7 zxFPXOQYuJ7R^M=W*^UyH?6mX_2~I*?M92*9;`~VJWLJXI`uzwB0Y*kTiXC#F=3$Sz5 zw~bhp3dEGLT(Lc@XSaQS*jdVu&YMO|cT<-!rzM=3fth04pT)s%m?9p*)h}8NOGsU( ztnWPlLlVN~vSKVkN7G*NJR(Cn_Y?UKP@Jk%!M9RG&^19eN~b7PK{qNL;k8l?8ljeG zDODC%)+)=>rSvBFwKqjR2PvLd53P65-(f?}q{K1j{=j-cDS>0Cbt7*R?>cLO5I)9S z%HNc0OH7bGQ7PmPdW%uM6mU!bg4`4H1sV0vO0{+uUsbvl!Cpc$16&982G!OJ=IbXP zg>D~6p@3G5M>H?ZF65m|G#|09=xzwOb@5L`#=nSrS}|X_eMEk>Uy4Htg=UGQa7tJ! z?9SJh&$AVGI0Z9q6=JqQUx>qCnP?ke(KO*-#9w~=pFac~(zZ7oK`=U;PZIEvpaA8& zNZ6l)Sd3+V{TRDSKmFcvIG@n2o&O7o1rQL$8FG1btSXeH3GVLC2OZ&+1%ISac zO}61+ho2Y~w{qYs*U)2y-$u^`-6MoFffuBH;$^T7N8W+uV~L-T;iVCjP*;}pBU=C) zVaAyR`XGkJx8-ixT=zQ)BSY>u9~kTm8|khr$3>s6<|_pL(oLq~j^Y|0aWfx&(b8pQ zU@>7*uJ0J`Gd`sL02jt91Jf&^=gGrk5NZ-fyM2eU#;WgTvk>bedh5*u!h3i4`|u{v zrBB<9*w1%15@KuGF(PA$QJM+v^rjKjG>UeVC==;m&>@J7RHt82-r1`F*m_6gkSDKW zgGE)l8gu}rkqr_CCKR_M>6AMPlJh-6u^o6VD8!#k_hF7Sp1_q?@f6M=fn35PDNNl8~_T#BJy&&Y!O4apm-4ZnihFv1{baKN zK3kdrg6`vS>{M;3a;VrbmOwlAkRW+5w`f1?>6ntnC|_DCB(*s$kc(%~fA^ zRUnQT1ck1r$jR@Wnws0TM!1~IB2uAvi~ZrN>}iT1pWYu&1tmXw*cX{?>rC?o?%n8R z4su(yqg|cNmK07*f$=2v)|Z!%2Acc;avI|`is40@nrynU{`g0c6$b6&@uV}TonwqG zfKPCfk(NiPTw^qDZkZLnsF%q+x5&aI#czM`7hpn=vl+uM2QcssM-bUXG0c>}G^oD~ z6Fc+Oi_M0q4I|x>j&Gd^IJa{`j;H{*dK6X0k>;#hqBX8{t47U40rq$l4~Ad>u)Z zg2af{y*}s1uOIYWcr9Bj!atG>HQ!^Imy>SBv8q#AbACuDa3e$ZH(IyAg?Q1cSntN5 zpc20s)4I9Z6<$W~mbo`6YU{RotQo#M1a&t<+1qhYZIjRPc0?TTQ(;fx9d$`FdG@f& z^Vim>ugsez?L$qC9wu0!7q`xyTBuSOa1twlZ5WIi>|6f89Mk%2R-aB5VX>Ed00xB` z>^we~HbLOIb|#+T3IJC)aE|TJIR;^F;2sgt+>hzeiiRukj1nLDUbI{XtyToDhX=!s_Mgn ztQ2x)sAn`3&XhLOs4PI~@fR+eH5YxFBMH+LE??-HG--P95zxfxr7$T+!Y2xw+=8XQ zKEss>WNLQj053TwX!XXzBHMqPVgeik`BqYoda@IwT)J++GJ4e?N3(7vyt`R26Wnu=ehG1gF-UYYPK7ZPF7*-8|v%V7ngZ0 z1!RNQJ|K5QDT6}pC|Cg#JrF;uf-mhaA#-L{vMxhkWqt{Jq69@;i0(fa+`NQb-OVg} zUs88frjU7z_?MdE*a=-`VRu-_M{`jgm1p_eyW7D)?Plr7290fgeJ^$f0^M*jcnGuJ)7l2{u$8v-$`PEYkCxX2xyZQzyUP%i8Vd;IJ6M&AIPN5-7Ysg zjr6nNjAl&^T$ZQ~!SyoQ(@}rglnlF1NNKW?U663z#HBni=uif%-X+lAhq@s1(tdqq zAdK$m^>DQ&qMVc2B9I;LN%+kNt$bsbO?PZB-d;jQO&9>;@-$S`ykiy2v?mTH2nh2* zzW1!w_bJ?XK#FC=`(*1z&O-2ir|Ssji#v63fP5to}lcgRH$r9=vqWGAXU^LAO$C-M~a9MKj6A5{#7uy-yMdr zhSaHd{unep4BUVS!gGxN!~gX<;SZCfw8;?9S2@Yq&^yL#R(ZYHVDolM7%!ftIHwZV zP{juUEITysIvIB=rt%P-Yrxn_`N_7OaT(TDR8YeW~_ zAWmpUtwnTC4yL$|nuXihoakXW+bVCzI11C?0z#KI$L>)7=gXPtetvdM7h=DC-Vr86 zs-xLB|Gqp0A}#3bhye_q6#!$IEBXuCwFgpCjnS%TOga7)FrXY<(E^o{J#VbhV;E6& zW6lzdqp2{sVtTD-aiuqs`CxbDP_HH)du?=PCiJ*qe~>|b{Vt8?kmbko1*>QKI&Pj-y7mMZrX}hRtz6+z zgzm(B^3|z@T76bEN5tB2ZIG)evnjc5jM+jQ%mdA0?<4QNj>$-~ALwKZ^X3#2zc(zV z-LNE5bt!{&#s(z=sw4L5H=B+t3g^_{NXbn~Y>lP6xHxzQM|b}rQLbCll9e% z)WsNe1WwAK8^TAcvNQJ28>|T0CjzsF{$PIc<96W-z#{f7Q7}=OtP*sB>H^VaFk)Pf zaPMr;TU5~mA%pVTl!IiQ2t)Yu+_m}lsv=CJxk@ONEKvsKk7wO9so_S;QVm zx_<}%Dq6n(4?f@K^iJk^wTE$RO6Td6v?k-9gHyp54u|UjUIYz6XG16H2(cSsGHM=rMMAfm!npRmj zVuBxvJ|7UJpx^Rj$}O|@V-y|=k3rZuPZqZ5uuwrbs-22H5|ua=a=)fQ3lokl5&y|{ z)#5&es0tQroP+lJ6<@`N!~MAt`On%aJys~e*7fgxui&WyDPd7e8Df%DXmJZ2GL^P4 zZ@y2C?r)wG-rm4+nABsUpa;k!Cof-a>U3PQU8;cx2>db&Hz+U%-@UcdKD%AbsWdg4d_S76+&3A)EjsOO+7duo2{%OO=(g^cK2yPZ=*BZ%5 zeL4a|rl&!aS2lVrpQffgNr50%VvfYC61^QXhh%%$t5QAQLI$C9c;h)Xm}E9$ioJmd z+|-07PZUPu`(i9b6H*SVXRlSgQ~c^1^gfqvpxDc9^CE1#s{0=lMGGT^JH+p*&?1Q4|6J~Sfi{egKj#3*|+h62=@4c)m&eAP?@J5e5nt;6jbMNJUTl)Ru@$i=yOk(Yu?1Iqn~qgnWG7zicvofkt9v__ziRq}#s9&W^j@5NU2W={{EcD!uTDl|&0$nrm!i1TMWCt)wtL$COU>`Tc78Ai z*2xw(zPdI7UHc5x+2o;+88g(ARGe~0YG|GDrM4MuP4ZOjT4+S+m>|VcD0TdXUi$)Z zi$*^ow%KM2cX(8|I3(DQ!in1h;X#tsgJYy5G(}>Ln@{S-i?(J8oB*l=-hGR`x>bC) z!8us+Q07m9#<*2)>7kGB`-VAdH#FtCwc{6#sgqa8<2|ykH>>bST#bi%mS6mir)RZf>0WG*xz9TOYm2`aQL8bewTr6iTUj<_DaY-P@*vbl2}HKbF}!)` zqAnHDvvKS?<;;3QpWtHR;Eg{UT-$VyGn-$Zp~iTKV%Vkd90SOKar+tDazA40_Adbz z08cIe*7r}+YHrEGb_yI^P z0~|WRH~$S%`%lMMz8sMW`y*MZKRU+$gHPN)9pisX7%`CJKNh;i|3~8dhZEFFll>{< zo79szT;O*&a+ji&FNJCZQcS4AQa@>Xt#_=9s45VvA#Mx*6mv1Q|CIXhf8r*&%0=1f z+ar@oY?&T!YfR%}Ypvbz^?FA5f!PSOqsQZphNn!joBH`nhK8Z=(7~c2uOMfqB$U>o zxY}tE!Q<8Ho48rzFAm>YXG6vwt5PaG7|Ax-3<(S|e?+KqhE+jPpBd!0T_n+bULv+} z%auF}F+u?ClNYsL%9d;aoYQJv>BkkdjQ7Y-cKu3z13Q9i^H-4caQKxgMv=1`X1 zYY#U=aAL=8s>B}76Ggi3v}(L~DrM%1Ln$!jiYBxQpHsxtJsy?t>*fP{9B=d2=ClmIO1Yx+b3Nno|3VuXSA1>5 z$-gs!-hPe7Detc5)HA+2w|pGS>@0bflsD|Gd*Q;m;J``r*^p)lzq#^UbtSk zFb33_5YP)X+9>+yh3LY%$Oxgas`<8NqNPe;tbUzJ1C306>h&~v@sOuv+KDmn8Y&53 z^8WST>(r+*MG4f8w9EQPJK_J!IwfxNKV{vRA zt{pnldMOgW zz7T;&U+lfIHr1lMuli?8XemMIrEHmfnNjQx&OQ(4ru{Cu%J-BB-`Lhtu=-6Rn;8yLGXGjl`HP`-?PgkC5KrAF^r+X zUgxNI?mm0GaCYz!-(hFLNfPo4tp54rU09|)LGW=bk*nwA1E6hLP>d@I`qzTem1r}C zq>?j5s-n=adzy`Ye>y1+I{oX~dlXJ={y8e;0Q29<5$c(f?OUvm-3&R7Da({q>L6|$ zR|}pS$pbQgfAlaj>;`qzJvtS_n35T0%p$>g6;`%TQ20EPu^SCS=_+1j)uq#bv-ZL# z@J_;?sp_1M_{Thm@Q)8-vPP7KT&;;A6pIlug`cM1l0ZtoHcskW9XwBlOS7yl&K`xN zdb*=iDvuCNTtL}hZO6bI;8HhS@oCn-=UbAkyKe1=UwRwP|NQ>J|54`tbuLk^0qdrt ziutZ@<}Ra0$(HJ#+7>60tx&_Mz&sBSPEVNrzC8O?>kFKBraP!0v+d(-Ld#Ba74w0@ zUeZXx9z+{2@<|)}doKlJ(}j57T1ipStmC;So94>$MeW=E>dW}`hU56#Mti0A?vW?) zC%a$9=7&91VS1}Y+0ZwQlg?jTUa{_*X9A zQ2O=ktxPv;p{QY;nJT=C{eTU!o%W2b7vna4QFOuI*`JAbfAX<=!4U5+afe4&rA*pU z2@Y=?5!~A2x`NDms5HFd`U;!!F?rjk$69bcFQ18itJ0N68EYd-`8?%dtNl5pf5 z6Y%e!gc7dE2!-}#eY>{{%ieLrvmCoSC+#t!Pwbk&0#YY9Vp{!7&aYwZzL8YMuMz{c zPg|}T^L;d}&gQ)|r`7ijSkvQUsa7tPdwEb+d@~-H0?E=_oM3H3{>F<3-uItEXy_xJ zQyj);1!8{#gk*~7EN9BB&v0;Ye#0q)C+@flc9N$2qa;TA8v9@eH|@pUWmdS@c89_@XaG+4Zi9oe3WXCK-#^~%hN zsNJtV##6!~)K9F^bv|2)eZjJBa^7I0UR7sf$6F##-azCEJ&U}r%^gm?#R=P-;nx(_ zk;%Ex7Z`{94UP$_?3u2y4Og(%g^wvz<*ogGQ4V9;hs%Rf+(yD<=ODU>uXsYg09F?b z8NdZy=~EwD&iJ8qawIZW@d@tE+47}T6vYn#Wkm(XQuWFV7uc_C?Y6B8U|qu#xt!-Y zW?f6bH?y}3pQJ3M?H7a=Yxfk&gkfRF+eyOJTcPKXUNeMR8HzR!j&mklqOaaam1&2U zOym}$ z&|!yGDLOWAQ z4*FRIHsv~kRyqh!NB@%vvXZ{1yS+I3qG_${43aQ{J+0G>#a^0D5~EwW5&SxJ^w89k z#l%y>+1dw`t9h`AJ4G7hoMN+^3w$YKQ(Vb$!CwPi7At$yo1lF3YR5UTQQSi|8{sU2 z^5UEKyiKTa%)Bq(hD6uw>dMHstuRx1lDtSsk$HjJV6*$K&Ln7mRG!n*J2XQhgY?UT z5!q~}Q|$S0!(5R5U_vYn^x7=B}^d~MgX$H(S3a;oZ z0)g-y4(;gXhhPQOf*sHN5#FS~6iXGN}US<&e&`5PmAVdbI&*CCGiFk16He+7a zu$fD@{?8tQyuP9Xh_hJ98O=FYV|~#IN;Wm=z;w#C4`V#zyBF-8CIUW=|KFd<+r?Tg ze=G&OIa|FovGOWrm3+Y#jE4EtML_#Pvl*5ABOdFt(%-u))LW|J8GKJ?^A`K?zmirAhq`W!)2R#9ZqW#b^E+^$Rf&my2z`yY6~ zH;Ks@2xW6Ql~h&nI`Zy#4P`M)ZDTa-OO3IoNgv?0hRNQis%(h5PV+N_GII=BZX7* z8%n)wM~zWF6JeW=(jPKdDBKq92)!d?4tQx6S%7jM?=YaL-*L%-+VW}9w-|X8HH3Sa@|(P z6)Q;x}$PCFIW2HJ}p)w2z<>@6(eX9M*qi85s*H>h^FBO`yWy==vT^q4>fmL*L0;jx{ z2bF4xBZz}tC$@;gGQ5cumCKS?jBuj$^?)H6 z&-w8cE&;mnf`E;4WHZG8hvSCZs#a6zSE{C$PiUVRE0e5?8CENpC|aF0dIF`*;3eqE z#pYepRCbbwJ$hWVjiT0RJv_p-byco0JgZ+j{n(FBotjTG$b_-6SgrtMSBQh5o(q$U zqoR(E)FNki+cp>&>qadc3xb5a{tm8?UTgM)P<5dM9%9Bce8mghkt)pFrz2ZcUr=+2ic{ zix$bmk=cicAPa&J!$FG-_)JF*TtbgGfZ(GKu!Ndv0wcf`f!f8vz=CTKQGp7QgNvS* z;gN&4lG;%~%ytPBhbu&Cjm!s&hYlbI(||BZ=L>>QL(IbA*@p;6lfW*7YYqD!58(!q z#|dy*pwLXfRD)^ztH03hS)kYc^iJ8sm$XQBRmHkGdIB4XLXZG=d>2J$igK0=sl-zH zA~dXGh!p$a<{nTa`9S`S`!OFbRkDseNYfZ!^cD>UAHYvxZyDkdJ@XsR1A>ekjYsQx z5}N0y#K~f(p3nZ@y3C${T8NL*@60X1A4%P~{P}PHBMPnM6Li!mnd&2clChtk*MA_5 z`zPr5XXLe}>_7Gaq4E8410rZ)9}bz+!%JLqxS&*T-EMev)I$wbT5ISydG>0X#7&IW zwVsq*1O%Y~R4T`{{No|~&(yMyUO;->^s*P2G)Qpem}lj7Mr1^CSv0P|BXdk6k5xz1 zdweEZQw-^{jk3Uo!-NN($!brP@p(XZu1>>^ z{@zw+j?$3rF8J%P8zA4#kbuj!2s(V1hs=oaS8hb~nMRFkF&kMDyy}#b0_pZV55M*} zLt<25Nugft<6{!ptJMT*ETkI(p{9<>BF3SI;!)O^LNn^J2{Yk9tZZ%S3rwZ?BXUe9 zXVpSc@NmX?yxQzk}*=DKGTliz`&UQ^Bw&^-)oB2nHQ!e+J|Ao z3H3UnFM1tzNTkUcmU?BX9K#w7W0QcMo`Wi&v)!g;^?Z0!k#z4RblShKS1huG= z=@ERkB;JItn%O3+w3d+Lx~+z-);#T9Xr2#z*G13(9O;c_UEg>z8#yIz!|>g^q}=2~ z_ZHTxOl*H@&_V*+%6Nh4 zi})~eyR*$zy|%pat5HRc<>!1rc$iL4y0Mmo=}X!HP!{6|D$+U$=C>kKSfPRX&8aZl zw?6g>lV2E6SWQSG41}qe3cpwIr;16#e7D%gpvUiENh3=_@$se-zn+?#Y02{RV1JWG z%J57l9g+X-`fGOR8o}t`SSoBlLMe3Tx0$SD-~g~8k~};HBRm*m@vFQ2{8wFi{2>9g z(WB6j-NK1$@K5~>punTl0KNnX<1cL`91pz?fJZ0qvLz-`GnTW8z05-Sspq$g&(C$N z8X9{*$g8xDrS*D}?{<1*9IsG+DN)|8TP#WT;S+>$#Z%K zTWw~HYvj^fbBH$=al)VR?X;>{+)QD9!#N@|;tzt3K>9!nD`|tYt&t3z1qdMP!RwvS z1^)K~D)b3_PstGRRuO=X-wKG$FAPV`_dB&>S)u%3Ti7Mcy^D0{34EfWg9xwcBB26oUi z*vbo6q}C*v*Af5i=Zm5xv6<`eB4lOx&L-tMX+5}Ksl`M)qmLcZtdW0*!8@&!(?ryu zUQX0OQqz4GQlq?)o_?HJR4Ys4yA^=P_XkK`Azo97Hs6LB8$9YORhQO=15}XfFRbwDrd6X}+aD|pyBgg81Yg^4(m8kjwIp*}!Dw&>uD_<+&34d%;0b4er zHGk5nu)Y()Xy|4|nXEcsV%XYt0VwmPx|Ns@V? zq3n=4G98Adi-74~`5;%)7sbJ{f(&z8Q7UK4(qyaSkHA-pmKoV{A%E1D7U!Tc+)L3_ z;OIA!D7S;SuxGs9VQ!aG5-e^BC_988_WoV@=buKs7b zTZ+oK5(F!jAg)vx{s=7IHFgXlE(;UPrmX@LH+5#ZwN4#j4tZ#SqP6N1R&QT3B9+T7 z?33!S=OF41wc@lO@8ib2--nMMc$wMy0ECtgR^2I~Mnl=T>7-<+%8X$R%4x_kJz&;^ z(w+HIIx|X^-a;5hHoTsrgWXph<p-b~k`3th~jp8jL9t+{YejWl$@CiZLtS?U|GaT?CFP)MUVtfMEy=P>U-( zv@av*c8zCPrz9YEpZC5-DGUExF?rzP!ESCzIhxM(F!_OPf;`TZ)h@U)ma%U(%fwb@ z`ZpRLbz5{7tG!i0@&TNE@=`$^VF2}y@eW_DvuBC(d+}KYRYs`p8^pgZF@Ea!rvIgG zkN{rR08ZzwbiT8!$lVN)rH^7`!!~OMHLoHkli<) zwwi)pyJ2Bj1cw|GZj4wefyN@5;?SJcwnR}#X0QcV*LrP@z3ZxmtLC(hm!aVRv>t`v zjz~?blzxT6ETHa6 znoW+BMPvL3@$Kx%K`-TNhnOMfiHv^NahMBm2f1-y>@~gQ;eQ4$Y z?xy=$6CN?&MF<|%%sm&9K$#CMY^TiMU#_DixDkT;7>^IivifKSw- z#NXOM;=P}Pm0V>&>hxG4o4n)-g89RCr#t$jGt(L8t(5tNT4ZbV_I=BWn0C&6x&ka& zKh`VFk=xw6#Qjp7yEKcS{Ir}&r^ukl;Fs)yjms9D;*l(XbtD`2^h@1oan~JdpdDWrKJBOD`_tyx;#q8_GgR2p75>VzW&Kfi-zb$)Cvv`n<5&dx zA2FH#)i&EJKmu~0f`Rpa0sHiv2Lm&=V0Q3ubhicAF}r{AV|ynjJAk9PrG2lm|4_P9Qzi){ra@bjBeMt6wooY1rc#mzi=v^0=w-@o{g>E1f*n(|17KH< z)2XxkYf-QBmxdiD5K6%g9b4bdp?SUcO8>+6qPO*f_pygtU#v{nkN0uQZAx%m@G<-4 z@OIHy@KFHFAXfN#z=fw6xT&(UiWEGLH1)K@i+vfW3*A#3gxXV$ar2GTM>@0-1i?J@ zydz0sZ2FMq>ueONz0--{qa6C(JEi|o(;L~ZPoS248WsOlCX{>U(^ezBUs>a+97=fq zU>g55H4o~o1)_H#YTM#{dq1c5GoCL%*c>EH&loOjS@a25d`x6<^$FNq&of_;vdwBNbGrv)b8Z2MW_tKd^;5+{ zU4!9i^lb{EWcH?Y2L0HKu2l2ehvEDnHt6jdlTHL1gJK|U7{0{oOeisu7Q4p^ITpM4MG6ayZJD{wCaz9xs^K@n#a@JO6#D zKs4q>EsZ&#W%c+pgBHQH-&I(BYBDBg{^Qql&4Xqq`i3BW5>6n1VO#xf$=cXM)E~0l zb?Wc|k)^?Y5}m%=rYzC+rRadZ?e3vnfl)#6a8Gh%^uc#78Dna*C+@lHEa}AD4-)u} zn8Bs}-V=&yk?Urm$Y~Z~o~cMW{;S%Z1R(q41gvJ9dRQtkm=_<8J&VWvHhn@oWW~pa zY5~QLB~J#mw2I9@nL1?@K^L6P&WtOIHw5inQSw@A`)_|TTr5mp*tB&UJjvpY(Nh%+ zYX1O%LLViBHHgwEe8xsEr)dA=HE^(h%7@MO2YnK6x=faMYRc(`q#aG>Txp6nsAZC1 z*aguLD!k3|3#+#?PZqv%7J^YNfYfmj1hb(v#g;s(mnb=K(WV$Y-G79BO7xd{>XOs! z1OV-{;0${yVQok|z8Y&tuocpYy-~C~*_Os~egp~1hk5LOL%5s=2|9}n=%WxAc%AEu;p>h?bl~QYfzjf?zonu1%A{R@0VPg3XdeB&9=6 z5Vk4+IBo=oE(ZPU90E`@SOQhM7v2*6k8ZC*`Ma=mGEZazg}dR`Wno?{PjJk_V>?PP zqyrQh0*99148j%t^PuRk5zs!uR_IJ?KoVpI<7QB{v)1?JuPh9bbMpzK2a4CkT1@P@ z#J|Mm+{qHdyc%)yW9(=Y2$c?~{gmf093S(>n!V6~J+b8gY|2*WP&GAaFMEAsrth}>~{cGM2qqksJ zz3Mp28G?*9M=z;S4T~H(Zx~8Uo|SXboQXBQVM5V@?k|#4t|f>Ow!Qf`qXi}V2}Wyl zSkhtg$mYd}aG2 z=%YDs#qJ&Bb_wgFQR{|gOFy0*CTtG=DQAZX=FYesfifY;E$$R7T_&&clh>Yn=*A<3 zJwt!1UJ>V0PrlFLztuIxfOd9x5nC0~j;qQ{cGEAHEjeyt(2q37;OH+;!*q&tQv7wI zmBcGF3roia$1whNoy$2ut;9~#BcpVXGUs4aZCl8g!KCKK9?^KPT}aPKAuEgVVJK~k zQeoAV;x@FhLEhE3;ib7%kR&cjNy3pgV}#Y1Q<+xFE9!85o`*CV`>b3`R z#!7+wc1Cmc^&lko<57b=&gAWsRkG>3xzNkSpPXY?M*`UEmw=blTBEXwpTO3}AK^oB zsdry`$0?iwbL?ae4v6Hb!v6H9bU36{>9+{p+Z)zyPDX0|4NH8TA*Mvd?wQ|ne15X0 zbd7j@Eg*7jStGnB?^mKFG+x`GOj9hQ!?1_i#(NgYpD%Nj7UJWh%6M(N`M4HAUsg(n zHqy-4cB!|1bcJK%P`u?f&7n)X6E)$RW4<|0ZT$^z`?Fh;8@A9Oea z>DQhF2_>IDsu}qxDfon>>p2rtk^`T13WT0=Fek3&cLwKwCw;||`2>ySXGb*>Oj{hM zMIzS(442w3e0_g-h*!KC7E2JfuoV^8RyTJKJr;QfYYXi|$+5IO(`#aQ#ZnIc;pU&~ z9CnitqB`bB>!NrN7uQP3G1FxLJjgC&M&X2e^BEiAG3mjNKhw>QYdlalz~<2FTZX9H zmP=~0rqLpH--l|UKdFIhsWGk=4tJSO$x{hUf5tB-@>$|`S22%{Zt9(j+_r>-(Kp0A zM5>vLxq2HenHKmcc1Qw}chQ6gS6*YN6ha|%U`BRHz*O=x{J$4uWchoe@B2FU;*kcx z4n3g>Fb>0~|582J7Pm$UA|(lw2H!<&swhqPpqmeX*13z;S?D(dNoEzqN?BfqC6g{yn-4fYuj;{rxK*k5Gg-9;vC@Kl)?8G>Gboq_y=w@p{H21jIi2>;E*4f8SwPhHl~ z9mBR{m;MtymhPMunqfv8Srw(>Zekr*I=Gtr=7XG5lpOuE5@O%VKWL+{cMoXMAc5wCv4-s`qdx1?*$pu~l>;NoETIYhGScBY?83D= zi;xe>hS%vhEd_s<2S-uR+!24XpzL;maLf)1CJ*M4E+LbYCE`Uas@F;wqa5DfWpvC6 zanTvGu|=i? ztU<8?aq&l&%bSsOR+NPbN%`{zkKT$U$ByNb^V96G3{)p1aigx@l;?~a^=a6?A^p<$ z8@jO4Ry$_cFl`QyV@C&(8*jzBqa0eQnY^!aV6|pouqF_;bVISc&HOS^apRIabF7eF zyx-W6r`I+wJ;pFmV~(<)mi{UzS8O{tIyV$AF`y+2Cvb5ogS8Y?YwZ>Xm@0bo_Qr@) zIXG3`M}E_mL|$6 zT z`I8C#FL<1!xsAJ%>;L0%s*1`$5j0=K6uI(9I4EHe6e4R@T>&!z8J7Nd?*M0z)-gh8 zLyWuX6Wu!!ir8L1=}sJ*V}wB;V%GUo*Csc+lVz{JP%l_=LL7Ne8w~bH<{busQaqB0 zoTT0mov6eeajyz>K62LVY)Dif=P%$c`|@oVPHP^%n~nTl1^219*H)7yKafxxu5c0` z*OxlGM!{|WQ#yvR5XpaNgH)v*)q!(0TY|^mrCmmP$X&%l?u}bFUIlK61# zW|vfy;l$Rdnpc;jg%|32lAl-jfvw)azi4t~D!@Z>$Xm?lJvUOsi2PVb54j&Ik}o!xc-Bnq-b0yf7(=h5Hs*=DWSX2(nGKn z6EZ{l7|%le%tRN$lf)2CU-5uth(4Pmf%r(l!zzrpPNIHS9WGtRt3yjNoEPXm@|%9S z`g?TMN(xqT%>f-~N6lmsu>T!_+nGI7bb~FSFFpbgo!%Cs8XFyfbvc;XNn}RE8}^Io zMb$F1--t=}hn@Zjve*1l&srr^e@UgBotoA;_8K#c{*VdTim%a)2O1wzIrk5_wBiRr zSN0z z9$%>D4Ly7p>nzjxmMB=6wvOtTZMuP0f_SR&|9^i^uDOjj63 z1Os2Z`>s`X&`H?uMl3r$oAxU`ew5>@@4(+cpd8}eIB-&{NZV>C{?f+Bd* zMCr`*v&105E;A4tuT9fQV8C2o5x`zosjHXJ`8SlAfvVIaU=p*C zxBe!DaE=Au+(H>s-iAD7x9aK;JTq>ace z`w!J1gQyep3Eto1N>buGFiXc*M4d}C;ulV=0qkFd>1334={;96UF<>wJR+sn{W3)4 z&*k8kBa1LRsIg3m>g|wq6rkGUzY!X_s32oB$Rm1Uomms+i)47C9=@exfyZ zhtig(Xm`iPEmtn>DU!w*@~3IEiD*^bbeW@Mis*L>`Q!70G{%sZB}*EVh>b{LH}`8` zC=Yil1qsU3DQmMljO_s8F;X&YZI#f~NK)J(@xQ(!aSUEC-A^K)0{g#0q5qi9|BXU5 zb+zY3u-?N`OJY=4BN>=k%RY5FAHlkkH`aY2br|`$%^x{rj}H7@ZoIA5j5wY%M3yd2en{RK%#!m7b%qEyQ4{R}E{Ii@c>R*lyx z_)T-W$2kLspU`=1dfb9?(~$CyXv+3$G)!Vs6ZY9B%S>YduB)6*`{uA|OZf-20O zmlf7dL;&psn00F>JY1R%^Ko;TVuEfbM;0y0EXF_$(l9nA6;~x~X{)dM6E;2ZjiPB2 zH}+^UtA=x+-ArL7m#4J5t5wbA>oP@#cC^W=r&SCgrVmw@rQ66DE9J0p+b$96#au^% zAK;HmkrY9sj5zLtOR^#@M8<6RAh*lk#6 zr{DyS!gQ<3r=_0u?2L1`l8FDq04DguYduz0V!uGyhgY|Bgpf%Q!sC7GEB#bJcr#6- z^7hVKlK>aJ+UVSa-5dG}t+;`*(&H&v-^(zvAWXCTC)l%CI4qslzo2n<<dtGt6mJUX71~a=B2K);-F5wuxQEU8l`gLnIDcHqogJlg{h`p6}f}GVy>(8_) z3|TH3v%HxHm<#s>BYzDS=GZqL5ha92y-`JUgGRVN)mWre^d9>vnl{{$Urbk0r(|e; z+GC#^=5S$WFGS@hHMBv(&t3H~WytgT{hLJNu%^-k{d1;P!hnIv{^!-~(;w=8_AnFx z-<&+$6#J)|o4gZD8}-)Tn#W%cQCrH9=cHXD!D1v_6ZMyHLpdmv?YB>91O3o|iM z^h`Ew(PdcT1Y$pB(U)1G8Pq!IImIJG!qB7TE;6x}i`xo5$FN$K3TydZYUDbCo@D<6 z3?{-JeiZ%?8?N192bq1(LEB}=Xtj1U?=VKSx}(dYVn^2a2_5uBl)eF0dYv2AUh1TS zi**0uF_|fn1vW>iK@=TaR%!}s>qbNV+cJ}t0WDsn<N}Qn;SC?3)WQ}~^(PmvB7FtOnRyewlD`ve7&JrR!`7Qx^(rS@dmhKxP z(UGuxBzp<593*GnLD8|g<{AQvtl{5?r|a^~(B=73>+{JOCK>p0DT=<_Pro8@F$H5- zDLNinDEj)Z&d+BfDDajXu_}u$qlCLv+A&K1k~n=^IUtCXvjqNFmjv7>Of2Ie>Jdkf z^o^?mr(hPKesi%^t)$XEN&m9(9tHheeGVdO^uPQGF*$LNeuNxLb=)&SWV^!tqNcEF z+v2s|5ZZpqBHW$EKN3A|miXbK(7+h7ov1emk6-_T6zP7@jQk|EUGi7?EQerXbmsmq z_iP~_`c6lZ7^MiyfRcUy>V{f{g1c)1XAT?T=FqX&C*zQ=(2b@vC)n){{OGdeuW1PB zVE<+o&{Mpr;+(xz*A;lTjriP@=n3IVsXRv?MEw_BkMSK}?i?{w=r1jFsdGo7+w3F1P=UwZ|?BlJfm!wjVQ2$!i^<<9YBYTv@@vYP1fkC5Pl zdX=(muQE+`Dz{sHp6DzUzkh2MrrTxw;`p3*cc1f4?teY+R9u~$EnM9{6_fx+D+>i1 zH+Kui|2Oe8*R?QNu{K5B$Ize=XuY*n5TQ$5uVKhrBqi(1@~yi|90CFXF#L2n!hNWq zNoLS$b5nzl^YyuOyMPUy3Nzfe5GC&_4gZfRs?Q@&XWU@u3d{M>G>T1yil`UVO1ti0 zRth0AfI>_Fq~Z5VE*G5C2r%+B0c~;dIHP;peC7Z@-@C3r#P7F-5%O(BeF0oB%`QWI z@qx4hB%F3H7wBOi9VtCTUhT<54XAX-&V!vCuj&YXs&L#I($;Wb0sP$ssJaUTWo2 z$j@oIXYPMNaUl}T-se%m!`p3(B}4+xs85mMo$FJKRo!57&N4&t!s}?IW6o3`bK}Lp z<91HlC%JS}FiG48Ki;Dg*z=BdiKWbCNvBK5%^UsB%RO+7HGLc$c{6#q;wvxW8pkqfv;x#qA8wcRl_+g+J={>7W2Ufauic5SmTEpTC8IFvX zdRdVM1xmXvCcXT!is8~nhx7rR@j=Lb($Y)`=6*SEMhTxBo4Y+fD49P&Nj{H&TI?(4 zrfRn~)E}+#K^QK)X`A6Ef0k7AJ^ObG5wF$v-+FE==LjcpwfDsYia##f&i<5>iOc51 zHI(GapawT%D#}jcH@&r8s88KuJ`m!!(%B#}w)d}B_AXA0QSGka7s#0&Z|TXoi7^Rd zDP6W=)l%69|58pu`jGB(*GDrRaLPjZK}SD(Ut?))z>ssFO0q&!;#Vy}+TOFZf>he7 z{lO#3?8hMZj>Yv>IO~J=S6i}@f=6zjwE-pGP}0r8>WN*)>Itzz2xl|h?{z7AD?y%f z(uuQ&!9e6br)K98;-PEgU`pA9fkg{l#yxF`6FME5oU-UUv`)$I9 zXkIAq)09>e_dk;@{&yq)pR&bbZFq0YW%TzvsvlI}9DGcDLW*F>jb+F}&=jmeqGR5H+*X4khMeT@Ux|@mGw? z`vRCpS?@|zkG8mj?cvF{s^<%%rhVd#uB4-_g8{lOec%u8!|si)>2MF8$;z%gaMGPw z*wsFEqwFExrR#vS-?tkECQ?CJ`$)M7pnTLU#ywLqCP3;nsp-u?s5czx?qQLT4-%=> zFpbX>rO?5Eg}_9-GW#<=HE9A!mlWSk4|oJs0n6drv6+s?x)yC_IM&2!;mOd!Rx%9r zC%&e#{-F#buF@rtAnY;%Kgo6?+mTfJ?sGp9l~pmd((tf#*|G)c5DW>~EeQJ%$_L{K z|1(}hwXsMKn>K@MrKYTlD3vh~<2gaTbVTr9qslRd z6WSf(xg$q38ngC3@is8B4p|#6XsBMAV9u1o!GoqDJ01xTtgZ)5KuqQ~G~^@4Q#HJA z7u=sOlGV#l2t!@sejD$~W1=P{g}h%^C@rPQN#IP)K4jg3cKx7ycGzZXyZh~8x&9%R zCSP#KMW>5Z1{Sl8AZGx1;C64hQI^gW6SGwS{DlXMsAY3Uqi0oi!L7r1=&RlzOfUW|F@`oJOCxNK&qeebTBw za-&NNm4S~IuVBJhw6lANRDP8EBTo_0d3#oD4~_hOvBoN3M_t>^tH$hHyJ-*?+NBKp z6!iPrPtDQW&bRunO2b$6`opAc&lK;JyVGp%LDD%RJYF87PYeU^!-~T1O#7ryrP2Po z+idT_f&!1m@E=LncqC~qc&fSq`C`sV(%@eVfQq<{?67aP zSkHwb7>SGmKn6324o06s6^sVP$ltE2jH$AP5;(<$3OENpJbxdwi!1&VmAh-{l!ja}FXFqu%7PCS%Jp~w`M_BH##HhKL)qmn(_%P@D5M7-hv zs=JIAIexHAJGNEGXF&V)jSFjgbtsr67P=39 z;Z{^ZO6)W6ei-(4iY%{(Tz+B~L`Q+6>$H>wOIgwDk|*YbjTRag@JgnQTu7Ic!>ZSs zV9J|N16w-Mcwf{_(P*J}eZ%0~$xbzw(2?}hgV#Ek)_7(4ey;}0U85B53}9PVr%~%| zb8gkSWyaFJlCGZrir0#&8L6K_P+l}IR}59%n?Gmd{Ufmkn^NS36WQ&%TJssu{Ki#{ zgE3a!^+1gvLJ;vGr0!#iUJaD0fuRyRjF)^@S?-0nz~p^rJn`l5uqYSDZzrE5Emqp!SdHn)CSCYNSD2weQ? zQ8Y_0N0}%-dvS;6CQ{QIs4y}N)3c;W{SG4ww{FC!8bW3&{=vXBJ z*GUG#D!iGIWY<`x;??L=H6AENyKfFDXoy=AEj&$7rpT{wfq{;>M2sh}+QoNmx7_Il z+tvk5M|H1QA!@|C;(#U!ovmSRGAPA%Z15V;CGU==-2m{NtkI>+I85Zbd+c0$_}(Qk zD+m!RePOzcgth{xX*<+`7^iY4SGl5}D2O+iMrBq*>l?~AW|^(r$*OQ`?c#$Rc2g}j zB@I(iJinTz?-JKD7|PnQ99#PAmhJ*lf`o%yK@hrH@|xz>YYmX=3xr2~EZ>rgqTC4W zu>EgAP<&@#XSHDUhUI;wqoaa}hqB@Lx2l4VBoryG&aX+ui4A|^MegCN1OpFss|d*_ zz7H?01>2!&b)ZTS?XiRMWWggmB&!^bdA?1rozt)XIB4=5u)u1B7f{HbZTAI74nObS zR6^KTP{v^TpnT3TixB_JWW(pk;|))BTssQOO)a>EOHL;43J`H!QAp)%v?$N`qH349 z%9jb>Ado?j@6Qr=Mqr2=f3;vos2o~p5uqba_NTZf5p7PEv9VtW=GGgAu#+**XnXUO z1;MI5;?VK9<&f*K$w11(jD18;K6l&$og2sG)0Rj1r!q}M&NkTWpL z0a9M6Z(Rd}?2c%X;SW`vZ{@n$u~GhvU}^m0M10A&5n0cYKQ0cAqy_=BO3UA;>Pnz; zyJ|=E&Qqa0(Ym|WEt^(*9{T!z5z`j`3W21`+g~&l9)}r1n+9_&4pvZD&39!Bx|O%B zA}+xc%7NiejH)T(Nd^C-1gm%i(eX9yAU7up({UR5j%XtRJUI=egB^Fq%}w|6n_HUs zSA;~jWbzC5gWKZmYp^l($NlOZ?SW9f?`EDDhT?Bbr%@n;4dkm2@PGZ+Q+=vGZ$btG z^Y}C-{{Mv#e2O?FEGz*Y_U;|r#1%Pam%tJRtuZ)Ry}BleQ586-haqEB z=NI3MlzTJ6X}3qTKJK;ebqzP)4g*^+yAZ(+hc}?Qz&;LyozPyL@VelgsCFEn#FD|x zLW1F7-CSfK!cDW|@sQ+AAi^=J@iow6O>`A?1{zZ9Vl4y#kmCsE2_VW;c{qLD7IY>m1F4lMYu;UiRN@et zMuE_}qyssiCZjfH4d5I7u3oBP$R^`5dW}_kAc!Ni6w}fC8_aHoivUBc6+0gdIEtfO zAGJH2desS;LZdr8cjGzL29Qm>E#-g<$T%(pBpMe02#>qLBQi#Xkc@)^Z*?4LHu_y@ zcArCV(R$)QZy}^6o3QZ0gEwNpC>_6akZCF@Sy#wGaY$PIEA8%xj**f#^zI0}u;`O4 zP#N7w)*E}b6kb^TNf$Vc?w1QfwC{}i@JRs4hZrC3X~Wfi8ZGQLhb*HTDL-ifYtj93 zK!hROXj{Ku6+^bopT7dL;GK5&TH$*KZnS{wItajZ-7V24!_-dr$GQ#qC&5%Nj7zg; zUm$5+R}?5b#LMlP1V~!{%DStpvnB6MI{pIh|LJ%NgoX20dO`#mW4uR#mP2~UUcY9& z#)3psQPG7aZqR|!xOpL`Y3i3hvoF`mce0e4W;klq zxk+r)$w;mcb7L{)stD}e*_M4m?V?xWGM3*va|Q#nIo0Vt z&_sL;aqm6#Que^tj{Bv1ay;Yo1hTNH)VRAr66dZTB{{L{E~Z||AH+x_>0}(ct2r($ zf3XYmu#C*D{YeL{eaHY)9x2vLu~0?#=Le!={mLnpPiMcqwppC+S$LgaQ!#TTeF4NE zH%SG(Y9@9~l4tPPepT(ol(#PTW+rnooGJNgPTE=4$)m@>0$K;6ql#S+wufD|sY~ab z!*ttf(=?KH@cxXBnlrI+hWP;;Hgl>Cww-kI5u$JGQTqwYg?7X6D6~VY$?d7dNpu>s z&Z+HUN^JHKj;m5j_ZPFR+I+gI2^Vo)&OOfq-GN+8pwyevQc6yJm91uD=Dpl0UvcVf zrt7?x)9GSU=BLcoPiB}vCCs!N+lk7SQhm`cAbBBXY(Q@vgEO++1>J=p{kMzci1cVa zE0Ip|n6%pgg*XL6f>}BdHUc3h9la{0nOQ~+e2e&~1JN1bfU1t^h<4qQWh`eoUvN6p zH^sWUmN$u|xY@sjSsp*S6oi%?d6gpBteb7qt8gR;?{L^`{GHWx1j&wNrbX%}CoQ$j zzqgtn_$yDt0RKcvlx3*VCnwmf|H9gw%NT86W5dsigf;JoR@@h!|JI|KrKBZUaa1$u z^w{a1p4skvN{f64LRiWzwX~#Jv;Sj1$R0_LW2EJW!0!iu4_lKWx=20ci zt_~i z=v=*vFBS!)s?#}TTR!R!er;YtG-vOQGVp4vcuHkrm9X(jORau$tUNGuvJ}KFZ>}V? zYEtFoOgIBL-p^A!1*3fCP{by!l6wkFzdS9zbup(%=516f z)!L0L{sBQrU+#w*O!p&Q7&gdUB&zvS*hU>*kPzdrf__JC&DM4M6faarFJX#mdsm+D zw|S+be9IEV=53}aqpE1CxALf86IzN*9f{ryo~4JC=*TTI80gx3m9_1EUBK}DPGu&4 zH#@>D$Ums3$)#$mtQ2VIpr79Gqe-LGvxNLd6Jt>8+j_?G^3+!)7kRdpu3x{$_SIU> zwvB^`hR>3st5x8~HkyIv;kFAsuOZJ#_ZMHb*Rwn*kd;*VDd+O#W5fuhsPZFK5S{KH zyfQG>#v8K{Mrj|u&~a*r4vC`i9o`u-MON${T!QQtC~EXiaazAUzpI?PS7eE%tzTgg zeKQSdOX2fAKQkYT8`M06I`aB?(WIBNlcF$_W^S+eds!$~3HdoSJ@HDCgN94hpOqG0n4XKk%Pfiif^Y#u2Z%suLF6P+1*Ur(K! zHF~dMpxk7%T#8S_BWMfxyLu&mt6cZ?!VvE6&nYjwg@Clt7Wd{F5AIHA?$W=1m6G%w zV?|CW!fj~_2$#2cQ8p`X_Iwj>w-hT=+uzNLJa|`+dbc1VTPuyHZVneL#>NIO zaaYB0D_`UvJ-18yGNze-bd~$c*WSI(WfDxUrRs5R%oC0{jU+{^T$%gFTXEdIPLu*t zaDTBx9TJxSfSTH}$OP;UKQ_7sET|EFa#|?6hoEPT+wZ%wyw}N%A2A=pvA6aV(x)?# zF0Wyd@MP%TV$1eIlg2XURrXC+#n)QXW*x$D=EVng>gLA-8>}@VcQ$tKh-5Vu+6BCq zT(>@F^{N4Z-i$#=h@s^3y|Zf)a09E3%Ki;P@Kg?p%tMdlf+x zU$%4RvkjA=GpB$5rYDYM5mhmhr3UM#qV*#0h}u~9chb5dv0!Z&wJD!|%gSf%*_&s7 z$q3V+i~lQMIaXNh(P=6SYK;iL!Djh)MJuX?iZT?>sQNOG$#n*UM%_!J#+KBjibhe7 zN?;f3uaR_;^wLpZhr~*^Sdcj z>dsAfrHc=_=CxgH+eGfXFGll!T`@Bs(1&)6Fsov6;Dgu)$L|PH)kL@k>r+o!rQ$&a zdtt?L;7Wpw>__8CHt+c10zrBfV4X@+LT`sX+N8cvHC_#nHGc(7DS#8=2FiR?Mv3yR z@{CE%cSdQ59XXe4y2;PUPLi&MnfyRuoI1US`c(fo7SF$>h-Uf^>(E|GhE0Depn4sh zGu1jmPvaN9j*r^$-bGi#ZDU)Kj``X2ul8^;NAb3nwX``Z#5sw(ee>Vaf3ZlyG@!u+ znm^HWfxSAxefdZy|LCDrtxo>Emhl73Ngd|5aHBP!5Y8Tp&KNqo)kWY+Rfi}9%vBf} zq2(enHrg^3TQL9vqZ7~uBMYwC2?&8%gs?>_!&b2p=?;K_ltQrPFi8%Wg^5OISqgCR zgx-hdz-JNdlYpdNwM>FmU^S@-uz^v)bxsElN3`ZL2@A-8nZRIK51@dWV9l58Gk}b3 z>K6dNMdp#~n}s1V3pRshWZB^XA0ez$=_7|!LaSQ|z=X~=Kl~mMpar=G#mH%r5#R}# zx^5DR%mB~omVIhV-+w{8IM9Lu=}92d?z5B#dNAJ!fuO6c<&1{b02cK|A4+e$zjT-#uP zJhXvjpER`3M8G(-0ibUL+6Ne*yf#s4e<;@(RB7L+yl$CSKPhCF6f8 z@F2N^>~KiPGI5s1M=D zwSxn`&9%eNVVK_L{wsS>J~9ag zs17_1&U+zX`b(z zy)^UiBCOP_Kd7V|G%>dN*nhYB*!OP80!Y;m;D2#k=_Pz;+96fEeE_YNociM5 zE6!dk;>2G2T#VZ}M?B+YWk0~Mm>S1W^j(~{Le44+QY zWRN_@fA`E*NE?ePplBgb|2+Fnu<;|}@6sV!edSF=9Z!foYMEs_CQApCtu!4T7mP*I z)V4N+60h(Hh<#_-jZ?Dgo7U7q>uvjR`FZk;ST}Mv{8$W9fplR!v@Jz{RO_<4RQ3N^xxij9`ykrE{P!#M1cBeJ@lP%Pk@fu-5=hD#+}r(HywFv-n)_K!2PG?35?iaD2|n#HxdN^>2cwTgW% zIuBO0$>nrz$XV@6Ifg1d1n$a~$mF+%GYNC|&r_>ubUsm=jTe#-#Q-5MNMXJ>T@j7v z&?*x<4f+N3Rt&dc$(C~Mn2XQZT#|CCW?-h{I!t6r_f=~HgkM!7sEtjl6qx{ZvNz<2 z+e+F$6qX>Lpw;%8+d<5&33ZO0fi3(-6`di;=MN7p<%T}XP{8_P2yw~8Fz_enZotsV zq(YRj&O#rg_$_4RUTdsQ;s|#aUrC@x<`H;8j+`QPW`VS<9_+8d>2hKz8glG3d-s1p zArBBz@&8cLSwEDt(0?%x{{6cN+5Qh1Eof`=Lqq&;SV; z3<85$eIS~Q2W*EhOazEWPENLFRTyPyv%)AJ;@eLdr}Mn`P7m3%0*VJlqZqXE19 z*B9vc)no|(Zm5~UyR)q9Gwb|3cZaS|rzb34pyuQX40Q<0LxW3Le(Ww?;8dJG8-gHj zO!E#Jh#DtOKX{fA+h9L`2vtAaI$)}P2t0DBa+YE|q8Wpfim#6DuUiu1^QxUwDsfp;ny7V;O z0)dxv8IPQ|T~e{8Gg-A9hZ?XxL83*&J5M}|i|wZyu{1i|9Y!BbskdnmCgU#GzQTGY zm9n}19wmKG#>vWp3ppN4zO!r;9LfcdH;YI>)(F8qNf5;+obZ(n*f@z_=&yupBV0m( zJC&m)v&GUQqY}{&(AtBiDkBjM(c{|#s}k4jwhs{XLvJLd8Kj9w=oQrzB^2t=ux)1@ zzonczUiz?(cAje#XBj4q^E(H2V&BcyZe$X&_`hVFz6G3PJY#VLbhkGXz%fP$S8B=N zOv)v{pHs_bXqep(Maa>QJqc2l@%u#o&3$!rzOSWxR-%iq#vbIrWX61~X)()oCM$ee zXwSX|HV? z)KW0y5un~ODJNImRWWH&khvvb4Y%S?!gLdy_pA!E9nLrB4NjyT%XbBHm?P%`$}q(m z#rEXy3qO~zBJ1WSVmc~G%%1nCg4eigHdBBs)F=ilA0l!o2OP09*zb}d-_$J9+)R$l zRQ)?>lPye143(6FUtxDe%()x%bgffvR?K&&;|XF9IzybPtA`aKzVt;x;1i0u0c!M2 zbKrM~nBr2DgdSE+NXtgf%gQU(t=#~9W!!xD@k+f(gu?;_i}asxt4zzWl+$k6@i1tS zcb+CuUN~9VJ@j@r1}}`)(@sq0?L24nK%q)k9zk2Nu88XJRLl?eVl#r9SVVeF)q3mT z7@}BFHz{-8p+vqp@tIK|`h@k)~55 zb@12cHb1<-!@rAM(#<~&rX!?r>^qe(dY?SB*?xk)i8gYZvhF%wATcI-PIEuIPJL!O zPPe_UZ}V~k%!Jeim+=n{tH9pb`7_hr+r`TZQfVvPK+ybg5+16AAV4$G z?W$^ zlVg&$W-HZ1u+UbUWpTLWA5O{h@tml8Y7`GE{o~=)V79iuCbYpaajM0`o1eZ4uRT2o zYS!holkTN59dBOeyVf}Dv_2A-85m-|5=#6a#CmE<{rqz=_^q4ZLKFn$NWq(5%B)E% zwgT@Q&pwG2{hVBQX40B!P(**0mLSz@HEL!eGW}TnyHEB9lcK&A8g6P@+gmk(S6Y z!}t`%@kL`xpbu z2BXg`{RW%jftV%Br_ELKQGy5<-?^yZ{tS>3-bC$B{Ad>KjbF}{ELBL#hWT=xCinV2 zQya|f=hf#{>4DQ@s8%^qn(h6OxWb#&F*v0>3euRA1MEh5dD5rAk%9?zo6&!Ar5Kyz zSyKg}YkkxbUbWn}$r*T+4PQNWqH;HYdsgblBSun_d|q(9?pfSO}UGx z!=kK^L+3+;XWGpun%3AYM;~8BJ-1c0ide|9?w&hnS-ej%P9vdSPGo6mUPP}e80QHk z4XOXG#+?vO@$!map5)o%RzD?X8=_n4_#st3QuSdbqU=O&iY^Xf7{h-7kJO_qF;U^! zP2cYB4!2+6OHsm3F4p7Xv!=pX=>+Wc5fRO(hcNd@BGfUf2!!P&q=xh$2myQK0X|aU z2U%qJ>a8GDNIrDgkgT?JZYc;%qLZ|SRhy3LUs8EJcY^1hS-R7OZW?Pvo}H8FuTu$B z*PvzHir;^KsMG7IS%N#u6k#Kor`BHlsT|9^sq|S>I-Xgsc#~ZDVsvpDHJ;XQ3(5}n z!$>1u9j@HrI$P|)zEC?t%LzuPad@Htoq9QaxqgkRvxVmNNNALe~IZ&>u1dono$G~7`gORgLAflv)HMiFxAeOxNDcuw=lw@f%z5%r23uId&16VW|Fg+J*&H#ss#Q85*^m-$(%cBP=^b2) zxGnw&enQMx3{UgmjXR)4Z+`(yNhE6dzLjbHcpL;s@ zE_wcuD8)Z?aUUetXQ#cre{4-n@P2#VA5BioBg%3=fcc5=gXv-FjPG4}A=Db)dZ0Yl z{rFC^-koFuhV4xfY$5Og*dAWB)Vp!Ub`kpNch1MUzS`H~UO(+~`)nK!fBx!3g}qsM z7wY8o3ufyjBK<_$RVM`k+XtfJ8JUEozYPUq3y!UODZ|Bu)I%z5d*6q0i~BnjhPJ;8 z#X2OMb@-aAcPj_LpJlj1>VBb*5?klS0H8aND&(a;pep30%xCY&utsXxCBamV9$|6KC zxt8G2DJ)I)w$+_PFbs_JAV;~BDs?6y!Kb{g?=HuKG8stB4HezkTWiU%vXaEW?YmYqym~aVU(}|by>%PgJQo*5C=A3E$pn-y(j*) z-;OTn70_eg%rux1<3@1Y{5TyIs`MF~lvJ=w(x}?JyBbwtNm7C+V&oYcwohPcR-VNq zgd6qrEy|*hwCDJVGC68jb0y(Mg@Rq1chP<4vG4*a%nj=LiX>ndtBc-g8w}w>%8``A zE{_v_cnINQEu=|44ULOG%gN>;ALNmSY`#Qyv01+6zP*dJ`xOW|*1A|`1?FS}zN|?5 zNP4sAsVvN$P_2zg$bL3w85ce+e_*l$S5AlPM3tEriONJnKg^g>g?KuN>0Wukqcm^H zpZIpooM3bgw@%xRQlecz*8|QdBw#Z`Rn&K)vxxf12^ZbE%4APK2da{jx4WSskzba9 zj-17q)G;41_>(7!P|Pf&EfKFa8I-!4R;V$8T^Z$tsv_Kv5e?+abcbi`ip??%Rn}V6 zdfQs)C8NEo8(PC6SwkLAZHHr2?Xen`GB{RdR!KL^UfO-EY8QRd2y5yWL-#}%JGGdz z6uT~+KdF)%QqvPkp>5xp2y1nUeL5G3c3_}FUXpz+w?w_!=bmJqlSsFd-MY##CY}oS4Oy|{m~X#420{dQ>`$;IaGqu7OPjC-qi%%gu5Ll$v50yx_sH3wIB!%vPGIp zi)u!glByY(g~gvgrrvgtFf-OyQ#~fiGDM!5k7fkrh>?ybL?2+FL?3B`{2ODDrHfxe zeX_TV;Jm}MBiYH6!XFBKwKtaFe|v3-2lB;Uuw!PJkGms?KX82GZbTm-Rvvu2QZI&b zi9XW1-XI-^+*rTIOg!1hyyb*Hr2DpSVZpx#5)r-z_kMi4gSb!N>JHLzWSaRXXZdx_ zXNub%(61@M1KB8c(N4a?C#>BofBR8?sSYS2yfNRETkRE8O; z5_Idz6wwln@Qi6Wt-x!bPjpq8#*!^Z9w}x?z}5}Zt4ETDbDK*ic1&WJ>`I~X2G7;} z<_IB+etXTyo6IuURBje=s5feh0xLL0QWjbri(sf*Q7=#go&EEEL1u0f1(U99t6mR3 z!)6_Q81rp>1gK=Pk~{--h;Z2*nst09x}BK-TF$oPCNC9`x@?D2fLQl9rt>>=WP&Z% zW<-nVwVJ@eHesCP4@}j)+@d~l)(uWVD@0;usmIe`vzhCO~XwPm*Gu48B{QOMz3;duP-3S ztg++S09&k+En8qg_G9rNshqa1h65n-V{tL5oRY4F3*hTx@gXU^UI@Gud3D*e-WT1O z*;v3+$Z(w11BRsOo8^-`}}Z{zb3|$x3QAPPu(HzS`o;o~8qU&BZd9 zY=0*I*D8jxrUe2=@u?!z9Tm?xn2ZDBt^mDzj&+*2JE^$9qCQm7(V4yMCs)rGgZ{!B zJ|Xr?NI8Gv5}k;{5|gb!^)1lwMbWn$ah=-1b7LKtC$l$}mOXWs9e0CJ7sDjeeS!8D-Jj-|W|o5W^*E z|M=}60bVgY+HY7}==9`UF~QE6I7B$rB}?AqG;3Qxu0uoEk0!x+3TB(`+~b@TIH8B; zrl4Fw1)C>nJQ|jm23gv<@Qb*z;NDM|Q8N}U`ru1pb~&H{XOV7&M3!H{&XW zty=NF>y+msTeS+AyTdFyhYa0j(Od1%5STl3%9@%kuH=)OjQqZA58gPzD|h-6FL?o$ ztoh+H-QVhy^p|mk$)Yyef(x@NOrx_|A+lxWpt^ig@6TXol&{3f3N#DLM9urvFmn+E zYiPxAl{q3=ZA}-f@Nr!+qY`%v=9EFD@ZNCbje$e1_!t>GchR7nGxXtKV7GAS4j|el z{VQ)2QAa-7{_)aZljFc$fUYt?ciCgVx)5p|+>=c`kV^tjClA{#LDEQ3U9f7K^$%V0 zKJXcn+}k3(CZ*VVqYH6ki7H3*7`g4%*)NK6B|Uz!fBsWjbk|;;BlZJ?68`|9|An^b zf4U$4cU(DGe)7i`5-qEb0U!_VyofCPsU=}(;#xF!zH#AL zoo1GOxi>b=B%HTy<16WES%T}FVm?Z@tyS|^bAEGr`JGtv;LLJA48AAt+6s2A-bJ(4jceT;#!qfJ${ z7O~V#R5jRx;~08g`bMb}-8W(qCZ7Ljc@rX_`VIGksJ#5R68{%?|3C1FpqZ_$qp`!k z@P2P)O;t=~q%Ropv{56!6oK_}1ZFr>$eo_q%LHU+#}j9zUQL z6@l0wLGGSK+)Ge?7Wwvb6M^-9H7<9Jxx z@Ez?cafI>zS=bJBY z3}mgM^#9;DFik2WiLPX2)GZ`g(>E^fgBi+RAQ*caj8$PFhe7mQQNwV_YXw1cQ8wSWF?P`u@eDPyA$f|*}9Y#)JaADoPswIps@UxbcCMQ3pz zO0Z13rROa5)Z@*+d{3d=WcLL?vGts%6?QT=%X`CD=u@fWGV&uzMarCaLhaN1jZ{N;%T^5IPl9P7v-8rUVMG6{orD5TT-3Tg~ZS*pE+ zoXMJmslV!{qv#kVqV0Kx8wWVMCkuA*$Cu}w%S0R_A>)x8(pmbJz$?f66$aPkOB$xg zEsCyF_w36+$fRmp1sU4fAWr-^>u+WcTBQO3>ASN&88X8_Pu3c|=VlQ;i$$Ri22(djJgp&hIu#b>bb6VqXO;8q@T@G=)(G6L z9~$DU;SQ7YE;-8tOzK|EF4Y+!`!VQVOS;WQ;lm-PT+KlR1nL(hvV5rCofvufnA3A- z?6ytNoCAl1+(C3OdvNYB^A?G1s_ zaerdapqU!Cx}D9O+mP0U_rFAB&eF@*A(K@6qrrgo|&riGtaj+#yb%b zifpv$2)#5m(;$)?f}t)59(NOnsGvaD)A>#+ZPY1b$;;`@+qbM8gwA| zt+Dh9@r*I20^Ip;DGMaC#2H}G4ncrFCr)b=YFwG0C74HZiSfWtU@x)LR}>5{=*RzN z>G6-NzC8mN6x7eQ7ya|F|DO?S5nBgqeJ3YlBPC-uClPaFE2Docv;X6(k0gA}B#vlk zDSZmtD%pYF8~tOfPNt-VWCffW=uU2)m<}f@{asN50pa6^ODwa77Xf+P%e9r|NgMn6 z;^hr$7ts?M*c?fS3*E@=q=m)EM2nDqA_+oyvCXhThzuX-sv9@MluMTtx-5B1B=L2V zH3Iw>qnt`Lp$O;6*PjcS{yXG;G!9=aE(e98RG_vLcu;(F2J)FD2g0y@Hux4s)2z>! zEi%kBR)Kaf;#{aku~ADw8SXz|pkQpB z%gmqhQvJ|%|Ap(Z_zxxLV54s(;-GJBtYq$FW&F=6<6l&3vBJ6xCOsUtAk8|}IPr!? zvZh2J(p)GsELeHf^H4rwLsIR)~WXPPHj!izTEg-D-(cCC#k zJT$k#Ry}A<1ZIrA=k}5a$LaHx%v~1BIbbK%3w(pT#@0gK0bdVP1os*Zy^I+5pd=TP zV~j{)ZlHnz64f5-Q1q~gVQT@ccAB(&shSS00S}TwmNsfj$Iw`U)JYkolEXeex(Fq} zEWcH@q@s-T5g3q5eNCy5f9ndvW4aG(T#{Gw5?OtPo+Z$E+`=xA4Z_h3?ifC~3>krkIzDdP$9;Xq_ zj&W_A_wT6~zd=wvntRoDHVIBga}TL5C(y`gXv2^HAZS0z9k|th&XUhRUO7_#=ZW?I z@zoVI6urR*?zWlB>qgQO@r%I*YU+Fr|Z>K?%%&YKx8}GfZ|cDJFT`| z@~E(MDFcFB4O@nm3`<=0w@CXm4I}I$oNh-u?1HYRGiiK`P&LYf@ocw!hQ-gFi_h{N zF<>UT!Rdg4_SFPkfr8c*1xM(`vY&Wm;M%W5#n+c|odwZVmb-xL#depk_lo~ zxfXwDqbU+MI_Jiy=&X!57do1!Hbg^S4CLaWKJeI}4hw3>nAP>sCPiS~_Ey~P#4Pr! zHN8gLXAPmhOteGt51&PLQyz-L_CPv~EbH|f;B&|bDX~6V2jc^V?Ge3UAg~nOcJ(s$ ziA~r{{a%43n%ZSyHc(|I8EtPQIlX57_3o`*GH``d0^hwvy&QWBCgtj(S`o-!BYRsC*8xwaL$n9|;3G zZ7qS%?Z(;+R1MAK{0ixy^o3ChDH?Xj)Hmr?D*qRf*a?ZJNw?#Xmm>}1{rhxmE&xN? zH6N7mDoMM# zth+}HeyUjlZQIaH$}?p^ekI0>GW<#42a0ND5t=ws^DB<)GO%iXhu6@Xhb)=&7EJr> zejaK~a8w}a2!oISzOZ^^L@+pLVw%GZ(mWKmGmH4uRlv2RLDS7duj2t4N9Bg1M|V{h zx4!;RFO?2kn&4EgB%zI-U;ctpjJ+WT<|l!>I6NJcj)7xJ5(2-@9UW4Buqsp}iGQq3 z>$N&Fy(Cd{@60!@L@(J-!n=YM-Tj6q+}m4*kn)nZVZD0?^&ow& z%=}D6tGBY;@{9H0jHT{^GLLEbdHw1oCmj!^Hjzm*k!kU-EQhLWAufFa8$WTN231?y zkkj4pIm`h##^q|BjN0HYod@`<(p3Qc!c(c@zX8Q_+cS4<-BIc5j-6#eDvh&gvbY70 zn`%{;aLYXSl4YN$RR(8}E3QHY*!!V%v3$qoJ6J#jf4A^t6QgFQv>(=r&2R?MZ~{Mp z|DB6TtqnwLf&l=~Apij2{Qo^zK-CYX8E$yo({}ASZN;)W z*|bWzdXcP{rp7cq{H zdT?!zs`3klSN15Y+g|3xJIY`0%_vfb05?=k!k?lkYg_&#+Nlx>rdO-{L;q z{_Q$f-|>UtiFZz)_izYbSYG2hM&z$wU-6@{=^tADxO2f z?&zw2ZKr*3-usLnxORT|mfd*1$ojm~l6+V9e3#V$eW?cX_I$(h>jgl(DV+%L!UF0; zDL~N;FibOwg8%sUgH=#0<$>)N&@ClYwx)?=Clc$oM!WbuN~JKE>meMri6E!$Ish9| zK*<@I)dg(X8KbG*{t@q8q`(@IRC}7k&9;SR^y%Q4dhlJk>2ZBGVo+(rR~i@xw(LZQTuaIpkItLZ?)(BR~`0z&nJ!jn~rn z!OEt=Qi@UZkvk(@;#~cAJd-r{dh(K$W()iojM{0Vv(&T;ri%=#I)TB;dsAc1_$inh zqdAoHfkTQpE`vfFy#aB!F_flBY5!HJegS7O=@99}2K=QNfv9CE^*I?eTfzD1?{|k0 zI>jY>S%0o7^ko^OM^xw^@|-qC724F9x-|7V?_{U_1UGxS_kmO#>T6mE62AwD(E74?D4s+APRTWE(t+sDm!7j}Pr#Q=yr;{TfJ_RbJ z8GU9HEslOQcgEztO!UUx@Umj#S(8#lS#A-ja2vf&F9_{Q+$2rY}%@VX9nHSDnyLUm#m&Sen2&Bj=j#;{o`b56a~=ESUh z+xpoONi{e|&CTBMk+w;!iAABzjDJ)QU^9b6o5Q{q7W;Dw`$JU6AXg28Fsy z*M@M$6!C24_}o$p3Mvg42WF(nqrF7GJ4?!7r8(84(IY%}=Hg*rSEGB-5raFUsLouo z#8byS+OqU|oz$A;W?9bqwYvSIW%#1tD+3Sx6$8e;a#Ctkhe2^=B~qt=6yR-UHlgG= zx47xiRrwC#Xh_p;mc5jrHFGg4D1>{{`kosVxpS!cL3jl;?0Iunb5j#?KKIu!jiLG) zXL*Y*Bl$PEJ7HI%iH+1WdcSa`YW?C@WOMsWG9^tzI_m1Xsm6fx9KyNgN;_+7dz11G z%La`dXd)`3JGcv&8{|f0-E{$%nkKRxaOW@1@E%Oy4UpP=pe)eeU_A=jV9Poid#d0Y z;T>qnmjriYt(rTJt)g%`+j%?DfF1RsJ31SrcD&t6y+DAT^|HM_{-C#UJ(+fVa2xXv z<8`t-K>=EX_V*Aa3hk-N9z$bR7AxwbR(h%AFK{l`5!CKu*1BQR&3~K^+L+Dc9uy{oW z)FQiaZsqS}177!a0B;4>BEEQd1@^G+VFPXp{l469~vV(AHcW5pS<9N}C^JXzq{OA4~ZH?r(Yn?mutW5g-e}1=#}0HB2;Z6f){g za0Bov0`v3ZG8t%IIQ{jV zN~ngX37L{5(^XuwBvFIlYTC+vOkc^mGSVAa642(X4B2K!8#5FP57%T*bj`-fa8&&l zG7=I=q^O_#!T3uF3$Cp7NB+J54q6KYr}U9GEcnu$A!iPZk89F=qCG-~E5M7|iqC44 z8W)%67PW7>=H<>fnLl^3*Sw)q9GVA4x!c9s8J8=*hs4O4Haqi#rv4b(Fd%7A+_56H zj~*t!$zAU^RJ-QGHLd*v{V!|nfv$O4zx0ENNd4u?W4j_xk;ZD*(}_{gVd}KC!68(-ZP~za zzw1(}kE+nGwnP}ul}|e|G>*JX@@(U$8C1yCh^V&(Usu9oSH=v!?H6V9M6`_I|ro#Z1FW>aV?KRLj|%HoymUyeLpvr6&KLsJ1vn zh+*%J_g)ZDkZ^b!HE~Qm{T2JGtU^nB%D z-?74WCtAP%@g~4w?l32Y*)}@BHQSb4%&UH3ftG4aqAj1Vpeoen>^oQC#OG4JZ4=uW zRNJ40vKvdS1prBbrp5D;g>{m0SWf3Mp0OKKn-$u;sMln3g##~OzKLbb6up8d*lffy zbZVdsAtp9%h~=$Dm@?~fnpkO|>@)2itRXiibUG|}!64Kp?J z?Xlsx^98&h_qt7hn%b$UT!Gih!I3C4EXUgNns12f>`7QFM!Bz(xT01qY*jYk+7To3 z6&WANf*tCK_ff0-3xlRMr<~ZAy;>{Js~3X}h7>>_Apma2mm8pGcw6cL>b|A*zEhIt z+9jt_{0_$r+q^4BME+(Tlg|i60BDjiFYDJA{&y^aa7w&5F1KZdrU~B01utkkDONr} zC-@dsFw0z6@X|y8hny}WP(g<=3QGOx8W+`1uyJMxv3r@V{pI@$FJ5_jOwNTs)hpF+ znjQt81N~EAIY33Yo7|_|l*%sTEfQrL{Myfl1x>M5eAw9>=;M5P*m0Hx?2&$lqah;K z;C^Oe*W!a3hgQ-wPDp7W#ySqzR4jvV$>ISF(Sk^#&dF}Ylj@FCDBsW#&@Nw>NSJa8`5Shf(7jVg!`?{rMt<+G;X30OC8TWR^nto=T)F*dK zEujYakai}E;$X|vxq$G{WaaJcNeLE;Vl{2t8wilc`NhPrm}`XfVpK_?(bg7BXpsk} znnYRhtt1k&iN_H#ESF%E>rAhT2$C?9sK41ryw5eCg0gu zxjwx8TvYt{yzx&U_dQ(z-4$GH#PNe2FZT#1$TyMbN%bJR&tr|HJOxnppgEcZJv+sC zM))xq#^XyhYj^0k7jvY@x@4lby$mpref_fip#WDD;d zc~=va*hjlcIz6}_sIp)T3aUAD6#f!WYdAKgRBr^->A4TcRG$rn9g>{nZD)ragTXOq zxKL^+jU55f)87LOC&hmQKiYZbPAnxzHZW8prhPZgP$V;gGy*?` z4{L|irg^)hz9p3OfDCgn&f1#UTbIRc6-33;9m6w4zQJs}OCqu{Bz?`9!9loi4fzJl zYW#lZhjGCYr$K=}Yw+gaLi`MbSvwjGaeKfZQ0!m2Nxf8aNuiop8&JZ8fjK7M0U51+ zN4>zzpUFS1#UTz%2Vo&o{7anmqL6UyF;nxD%~9;l16-PWsh8zkD(Zu@$_I#_#1ucr zuW|=qZgyK0y%tZI0v=Z!lJ-(O1>Y!LBTTrE*xq6B2Ch^Sypy9j(xMr3H-j8F;Zv#f z873}u{t;aor{;pVTCEc;VSFlN0XO8?@e_)WGT@Hnt&Wpd3vhobJ3ycCME{ZR|3G#u zGw9Hie~_JrA8=CZ|ME8czuZLs4Y2%oeCOZ6%4DVg=_&z1NU;(DgFnbG;7gJQv_i`D zGxOI7M)s#j0%{Rw=;>pK>_Cjm-J`v^=v6!H9UBeg9cu^`H5W@xzR36VI6D9G@N7R` zzP{!KK)NCD*MV9W(;pae-k{qn36{sS-)OMwz%*nTqSt?7M7dfK+pB?wOdJ}nk87fv zoGEtgB2&~OL!zAHE~DroebT-xN8|;23a+q{Jngt1(UI2(k$9;Uw8z`7onRzlkxAN0 zUZYgZ0{($`;`NuzS>;?3@td)fhy1H(fKDWX;v@a&_`8~ zPK|Yi;-jdzOx507j6%CtTqFtZ#;y|LQyDG74GV~sb}w=?jd-lnXTMoN1g~QjP(dpX zhG(vgB7JF11Vaam-_rar`I)I$`IjU-8>+w&NWVeC;1H)vHJLVB$8qTui?M|f^o25EcG)AX_U>^>xup0EE9 z3Dm>Xj*kAYD`r0;0jB@!;ry>Y3+sRSEPQCd)f=i53h;{w#^3yRVzVAzuU`ki~YGv388fox;0K(K%^pZ4vN#a*X+l} zwoWtHQN`$9f)|XSSKmrE>9BR99Ue!|nQTb2?Daz0-lK{1oM_C`m)Fzlb-^{&CLXUj zluE9qX4j=jF;eHAs?xm5m{`q8sG)*HQ%@a=GIY;*_^qLe&ZSO;s>Yr}DduRou8g@< zQXV!(j>1aaQb$p2NTbT1qkcTrA_FOuT#dzU+opi$jG&A?C@>kPMzG&(S*Z!wb-NMr zIP*67fU|m)!R|dmkgbLQW*Px}x7eVNl9CXq?vE*I#V}Z{Fq>Q>K??KZMb>0-MTz?7 z;Q1m$sinB3Jc5`vc(&yrgGX=h2Vu!+zzXd@wUOfAeSWcLZLgrT-;QmgqF!c<4UO+- z$u((KQ#toJFeJ^@Dop!5rA9gCBW9#bB#(L!kG+{_vE@*t&X zCo?iyaDlJt`8JzRwJ>lgj+HK|5|DMf%Y^*_6ojYG7-)MR6o_|^6zIoITJO~9^x>~D zP{Xh;lvjW(aLr%EP_&a_WFSH`Iz9b~9)?o7v2o-a?cl?&kbqy|%0QM>OZt#d zOU``_hpUuFMC0wAXI$MHBcm9p0&N`ERf`pmV)D9eV-25sQP?Y+El`7PADOD331{r( zYNOD~T9^LqPOWvH=gfhiVeIH!strDyBwO(>L~!jh(KNObABlc0gm!*3+Mj$nAeAvc-&f3t?=10)yFPjDKT)CC-^<}aUMej&z_nqm6{k4#Ku`iAHdHVBSGG_Gc|`og{v z1*|jRwTaZQCHcS<6Ssw$`Jlc@Hj?P}xKrBJ$D9M__B|fnGk33R21baiq1+ahqO-5_y6xrDe;4VL91>>qB;8XbymEf)j}i zb*1NOg!TPakE3nU0>Fj5lV&LB2#6HDi*k15<$qxJaDj7=fu(3w+CL#IpI;Gq!WH-c z{de5g$YEj^>4$tx`iFr1KQ9eZ`Ub{U|Dv>`{zwy?=dt`lSbZP9wE| zbyB8j)+%9%lJWboSN^%t)eZg$wBLsJ1pY;S;`BphggjF|J#3h1+(c>saY~Ux$$5ar zodaV1cd`rtf;(T{do?2<;fR5ZUTiIUq8Adv#rtg1W<98fV{RCw2bR&jFiC6NnZMUpL!gb(d$=NHyym1Z;pR!H z9hSEP^deVGY7Q^d&Yt@E_Z#czLyGtBuPi;vpwK1&nk8?mpsY+D7Vsu1)Rek)%rVhd zst8i+s{vzHTv><0s7tsr(={_s89PnUWvi2;a)OJd6n2TS*}tlD6+|RiG@##LiBd2F zwP^#{V9>we&u+j&%MtcL4MQD>s#s{%b7G6cK_!g$BHnR)3Bo0ZA$E7RS2Wd6x?9Qi zvj%u@d-nU`vK)X^X_hQw)8Xm*{KenZaK5q5%K!Y0#<88 z?$%K^Fpn&1kps~Gy_||a)(|-;000N9004CVMLB;$YWZ!9M1DeQ|JNR-3F?J4xR}dI z!Ksl=Wqx2|Xr$R}JkHZ5uSHhz!Uv7a$pAieW<#9q-HxoPB?cBJ0!9ocVE~B#I{*=I zbXBGZ5*3dq|2H7Ge;gor5+B5A*In1@>gM|5X#4*BHS6`!c@bDZK#-DJC_Rw}dR4GK@@N>KJ_tOBA%#4CMIRX&MO42q#Q>fT z2)J0pijO~cULQYOQ34b~t6H2Z_?-HH23}@1^k#d8?qmY^718*&|A8Vz2hzGb6rk zisqHrO9+Ky{1c2hjP4cLiwKotbYl%7OYfG^>x$-;+RF&lJ-nk1GEM)&6L^gN;n({U zy&KjWi{9C@BMic;f1?P(Yk1=bLQC(K-b)FkV|)V%QcLfa-Ajx95z$+V{t?oP3FR}s z;|k?7y#ovNHL@cM^;OlUyYrK&D^08#38M??iyHkE3iz!w>U={B@~yJN3-KXK%qk$4 zFGS1=X}HNpE-^+@xXGUHKQ5ZGL5L`jgCP7d&?E7h33*hGhKOuoDPy~EzNYeBz{uv%iHpdz-h)(dPH{r25aN`I zIij^mWALns$U2&}=>wLCD;Z(5$>jf7F$+6K)~A z$S_(og9RPO)QND^5Lz^2h91-CiH`^@ok!fPV3f|pNr%##jR$z)wf=*G2(96RLl(kA zS5$ZG>@y`&>9zz#32v7_KHIBG}FZiKKQL=7hRB?J#J zunQ1{&3Vj|v+eo0g@!_FSzSF>P29K$6u~_gi})g)pmEq=3iIeE5Kb^oSLWB&XIATf zC}-CG0xAC%dLoM(bYZcuJioL!v)lh&f-+K-ZQbSVe^tZn{i$}s&gV9)WBqptZMJ|g z+B;m0jU!;wg*onSspN|Va`1#X4K5~AIaWxhOVXnwv*X=gN!i=3Tp%n~n91KnhsRXR=&7#S8=o{HKHIS5|Pn3O7zv@Gzo2VT3k(jH`M0 zjnC#hOcl=4@rGR|Ejy3RKwT*JYNUK*GU7IJOh^<^wZ}}Dxd8D}tH#2zRkyOFl%|rc z%7vcc2f$LfJ)WMaHDbt4Z(7X$$a)!j35KE7M%dY`kjkPLl{X#+6M+a6HNqJulI)40 zf1cT7)?1H-EIR=@v>Y|X#ZD19)&-JbeOP{QQG3XnRu9sr;aZptvI500WE?Un(tQk6 zGdCLjaG*f#<7{jXr{97MzcYHc3n4mm6E@VDN-XuLKs(g>7y?oxIz4V9UuCS0XzepU z=g7{a=!z_v-My zTn70M#U3?LkHFY1FKQ_a&nf}l(iu;rH}~MJ%9(esI>E*_$N(>Su{Yt$=@1g!PVE(EmBRmrvUdy; zCFs^Q+qP}nw$0tPZQFMDZriqP+qTW!#`L{sV&;D5&zz~Kim1O?kt=gmuJ?HttM4Zy zW1BaNXdKm3S{z=fp3+&Wk2BDW@>x+_$+?>MlW#_U@+5KoTx|iY%wkx~$g@3&xPk^# z&FE)g=$KERIDQfQ+u7>bAkb)`c0D66%iQMv#R?ZP z!fMdCI)_79JtfA{;XPXWPKW-&ji%T2H|+QH_rD4u{QG(Fb0ZF|(%ANsqq{o1$NACo zR)1^FV~d%Jjk1I?T(rUxR~;Ls*t?DXM=N0@Wh=u^zW3m|i6p*vn|G8NqLZ#I&C4L& zzT|EyE!~$nUEZ|5XEt(VNMbGofCj2r^D(036h+>)+I0?!v}KQ>t+fZ=FZ}}EDsiE} zN%l`3eHDAzwi6Vk5^U~H+)g>^y}+~iR?4`@^I|G5dnQ~4LqhW5TygnW|5{oUISj^n zLg*I4xGr=X&3;p;`oI_yp9Fh}IMjt%4P%X6cXb*h%XZPxB2QO9he;jK%O|30 zPeaiO7<9v`%PS|D`MT~V1Q~NiinI%O!li}rlsUu-`we!4tZ6`M^S{&#l#Z^>+B)xu zyQB6~nHk@a)LY@?J6SzI#DDr}d%K zhf#-axPOc?R}&#GksKju$s7S<{40MQ&2VY5wJ4cHaWMrE4x5;;k3^s=aD_2kx>} zP!+wn8>vfa7e$v|(TEPs`fLVh*6XsZtTL7KJV$h5n>gnc15A4`WR1(nJJ>fh2FEx* z%Rl6^$Mm#zpy9@?I!6Ghop*c@?>+TGRU!=11oUg zMRbGTsPfgaj6EcK6W;$SDDl~kazOV+>=?#mzXW@`-@?9#6@#OEBIkQoerKMV>Dc@Z zW+RCj6w3Ng<~}w?XQ&Z>%2R)8&c~xQCO+{}i4bndZXQY|waxzJYt}Ouk>XwCzXru* z@FOC&k;JQ%VXfM=EIZMBA4ldaQ@zrtpHBJSk$f~@*sv?3Dp_u|%Uqbk8lUflOw9;y z*3*+=#KWeq>CFmEq(}H*)KS0(P_{Xfs40+!s*B@#g!E8B zWthjc^YkF%+tFTGS*8Hf-!mk14s=AOTwlVF4Yi=+qRAXpF|A-91lrJ%Qq;0@RBZ%!Q{e2T4%0c(6A<#|hY&juQh{2E*jCC+PtZ6|hEuCpC{PZ#xg%*ew~`XM z15KhSk~R$G(?sd+iHZLMw*9xPW9y4`J?_Bnts94i43E^l&D^=UCEQ3Ij#6gcc&MtS z!>}5upH99fTR(M+R8Q6(tadZuh{QDu+Pn%qNpsq%4jC#I(sNVTThu#Xn`zKG=;9oz z&1iHOuCc{Y7-_<6B8|)UZsm}=ZN2SiSB|Ci7X`)oE0 zk5bFl+}cevTI1A8HP~dBN`I~wyXx${?@zZ+@fu{<{i*|Q*lfDo@sZ70EIOq$R+%Z` z2|eUpSfeZh*D%~UIK=cieE+9~C2zOo9{DTw*!91giX#@{(@ajcGt^{?y>GrTEBRK` zP7Rs7^{|*Ikz)*bdMC6@MjE=D2{p3xB=^!(cRg6Oy7%Us+YAtHb}iYMJpEw0kF_1HyzGV=S_S2EgQ^*XEWFM; zzTf{o_0StKfr%sJcnoxm-RydC0#Bz><*LUDXC4%MLlp|kAMY%E&r)+wH>r)~#1y!A za9$Ju+QxVR2Qy!F;7#d+Q%^r-1~`ohN(^S4i_+0nxvh87&s>!NgkDbwLg77NO09y3 zCnApcSkHp{SndFyl-4irU?byGn;V&LhLv3xms6L2%b@Ael)zvb-l6Kwa9GDf*B<)B zF?x?NzIn;O?XPca^?(HL`$4{4in`mp?(gL3DP{d~XOA4xj=lR$=su=L>0`%<&+%ERzPj&0KUNRGU>$9MeyFMJ92fkPKkRO<48WRms}4cGUV9 z!=N=Dba)g}xQ0e}NlDY+z0 z3nq5ilj8AD7L9l^=tX123>vwlqy=Nij4W$4<1k`hq?Px#J+6t?X$08hVcQX^w4tf}refv5h~odipDrdVtFZfF&^b z(Je6f;maqu<5M2xiuiUyngZR3vxmLnRUaM`RPS|*mb>67ProFrUI7-?y~!6iz0nui zKW!vV?*)q<-)xJZUqL0%A6kiZhIRv-8G8}VCB5MnNI$bBt?bc?KB`HEn90-b5`g*#%!6W-ecniArOb!O>> zIs!)HZI2v^GTnjBO>;y%p?2e*{`dl%TYM5MEWQyIX}u0hv_7~IZjbE7ICJ-coMV3S zO3WUm67fdqg+5vPaxRd4>lRIa`bcQKek8O!z!TUU;)`s|)CzdQ{U9vheo7Sqe((1{ zIxZ_#dVOTSam;3P9Ai4N!kt+=*Rs!r9&bowVZh{g7GC1uEL`2?~=6lGzR#w z2-{*z#bYbJmcX=Q`th+z^6?46-A=@7#eayX0r$FO?_s~%tlW}U?{2P6?8=*r;X-^h z9oOe8kp6=T zCDA`0qUq1b393p;+6P6Se~SJjr-TFQlYUL1(9lgse(?zF4)}U1`|GAi))tLdGCf0+ zKW89&ZAW}9M0`y|d~HO0jYM=Oko6|l?-q~Wnq0wKJcwg{B(1N2#$JD0Kd8X{pntEj zyk$oJWd96C{3QKMM*L*{j7I#V{>(=7Y5)(JS^`-5o3reP}xOEcvdWvO33ynfRs zw`M{M)!X_Uq=vR*bkeQg4vsSjiopUyj4{GM!k`_!&jh2U--b3Qi(xx#PY#wZ_&0v! z4k1*WktbxIvk_RSL_@hGUwNU5vXNcMl)tEmRe8al(xUz^i@VeY8!2pjBAT@1VNubr z(n1-fMc7|!N2v`$QrYANtRFUvCM>@x%g=4q!`rllS7=(`U>f49W*7&=P?At#47z@M z&QN&$Hl#sTjG9&$CyYUxPkPGlU6dcU$lmk=KlytKGMaxPtwi^?}rebMZcrr|-AB4=9d4@M&Qqge9 zCmHLE6n-$$lLD55lIl)4mI}k%@%C;NgQtle$BE1tbjp1sQu7V(`Q5$l@9crw5x=8) z*#|RGmp?E|3KZz$~M2$2!d}) zi?$aYQEg9tV4yDLu{SzxpoK*pR5hShklvW*b_@7wx2GHG7v($k5IpJp&`|9!1GOb+ zi9m9nn7O{5?qT}8zMk9L;|p$&EdS@pK&GD*l@~+V0aFc#u|}ULpt7yR5J{P;=1{Dz z8k`6-Z4pWdroPy_rE98Bm-*=!=lo6k9o4o~G;b}%|;Q<|k$g^PpVVYB=AAwz`-$#*-Vd~uA#A|Rf zpX5+u#-as>vM5&)&#}o`m$_rckx+QLrH#}lblnFc==OD_VNwfr@`>q3q}m*#pCU*X zRjiTri?+w+HRV&EdxkA_IRl~PKJiM0a8u(Z1sZAr%hbSp=!Q1*-l(GiH&P2ZXVFzQ zf3GE!yRC9BC-lQTPhW0Om}eorzbkY!K)sJ>a0#)9eXCK17<fs113Q!hL&jFG}g zvdTYfLJbk;z+Y~&at9){S5bM4@j;MAI40`%Ku6b#^u^T z#9UTc?1APQCf>BjXwgf*6QT!XX8$w5D@_T=?GxLR?HS<>M~nfe+Z`c65JqufLg29P_&FoEdB}OkLP8qv`$%%no|NZl| zwa}GVHDvvjIMp?g_4#=<)b+P%X$j{Gfnb-NwRWkIgq3|sc{n~HARr-Nl$oBsd5n|1 zzKx?fEkTfgx}HXnpPZ3iXP2LnnzcPRK`_aj7!$=GjEPYI02!D$%@o{_E{}s47&?9! z@V~1lKzK;oehYbMzeuxJhQFH&r^QC)X}DVx3ab0$N=)z+rs&+E%wLcc zBCCoJ5RKs3caASYhJ%ECaVMb*rl(7uMW;#jV}bXuy0N)k5=sIHTue?}l%r{gW*!Le z!fD`7mtsXS(i454l1r?Q-}mQy<~Q#gb=*+ld0VsTw#-DU_@XYK(GU#)#%6`)hI76D zTW5jwdSsTGfb)Kf=7FXOm$TmY((dDx)hXZ=(jU{vqZ*^%pUf3v8oRJpd z^utd^o%b!5o8vK7kt?$(;MBYC$+=uVC%T&(!=Yd!^k_;A)-lUC%jhW-Nmv@Z91m`Y zRk;ga4RoPrfz^RGM6ezNQ#bYp|uK z`pgbnUSIFOsl@1sc~~ZYmuTkynaA+|F(LlftB9k4nXH|QlgWSJe#w6{{pzUmi#b(ji*#hg3rVscGJ$u$ar zRF+UM!JOYu|9q#pxl7`)gr12S!|33lyZqI1nKAhCmTI4L#q748%of3V!#c@fu*3kp zej4b6MZDw(xGc3j1Q06oRHrrXva+zJh=YD&_>qEsAVvNKw zM(_QBq0EXkz%eV3AVwYrAu}2~pc!=PVlq0)Jm|{hu*=x@2t|K&o$redkOnh_)kcN5 zR4L8LKuV)oo2X*r={Bv-+_-Pi-qma6@e-w6^_FNW>k`wgyeQ;J7M0e@(N)=?RF1NW zJw4+La5}#|OVB`rnEq%%idX4XRC}yZ)>Uiit9Le--l&tgOlMmTeQ44+a*s_F)S&h& zFS&4;!H3ubKBl7XtG1t6tQfKPgSO>*-`%^;P$_4K2CX}@qtH4aKtc+0nC^3l;M`)c zS3W@VWn-Xsz~%(*mH3PV1@?##-AbS!AXhwE*4au@A5e_=fWglrxkrd2Nl`@Nn1XMz zSjpwo9X3XEtf^QygwXyn9aai}^0>7SrdJ59v;-Q|yBqI1E#K&}O5kz{$QO`aB|7lx ztu1GAC~=ie`Bi_%0xgnBE7ZtB_hY(GT|+An`MJ0m2oU%g)_!Zy72zFuAVN0< zKY|<*SFq0$s3CIFEdkkgUr(!GN*^MQ%_+>>FlPZW)-^8HWoQZ|0#WBH`FmuNZ17Fa zw%`x7wjpypaD9+H@`zP*67d7n=(_XQ{AxJ!7#rfI7>P9Ovl~aO3unnD&Y=w)Cp#O< z`zI+FgwrtFfyT|kkO1<%+}FKrcfke3Iyvv9-8?{0W4`g3&lxOTUH#9;G2Qs8z|Y)N!_g710TY!Y2Pq(#82@5 zsi`r??g_5@-NdMWA$HOKUvc@LqUQgM%|BYoivlR$Ewm)C`)HKtE(jpaYC!JCL=dPT zrJ~~T;NDW1HjqV`8ATF)k>7y1*^2hw_TJCpoHTq z7^9JawlJa=7^->uC4q?16K%zNY&T=!lM)mXM5M&D#KcB40B1-esLuLA{UkS1ZRGm_ z!C^4f-z~>6vCKt|WUJJy`?naK9d;2N*BqUp4ANPf?_lR1be5HTwOzW|2H`cUA-&T+ zl@eN0ngb7~7$XO%YsB^A2;*F2W@lv`a><>(yqz}TOqgN^?YKs>bI@A|Io}U|-7Mmu zb5*6?hXBA_R3L6lZ3Z9PM(<9TaOmK0v-9Gq-hXSq;Pv`!qz0P9Fs7Ksn-un5>kgzm zs*GVCp|*<;6)lRVrn+~L#DOBsn8QS^?N+aV4-t`|GKxsvJ-0~|oPohgj@r7e*%=Hf zG(?jK&`R~SUbi%gE4lc}O7|#@NGX444lxHmhvgl)vZ;fKq)<+9ywnPj*w&xWe{EGr zVh2yr$jvSmMD4eMQ(!kI&PH`P3017H_D{n zHZu&A!-fvp{o+`X)(j=I(-?*Qsx7hj^f<~-3rMHLvM3z1-7IbIVLA)dVUPsbutf3S zJ6o3MNW1mjRTcGMMv`N^W9h`1m#FDvZotx~S>Arm!>g3bxro|O%$O6_h7fpBu(p$^ zpR5FL6R$+?LJ*;_GM)ezJSCwM{v?*uLy*EFelH0yY7aLP%S0g1lC&y;haW9!P6BH3 z6QryZjfluvG0E-}^1fl_1B>t*GF4N`#;>H<2C}ej1N@sOyXK#(PvI0ebtYj(c#Ett zl|iV*MxhQbPk>3XmMwwn&-mUUHVqR0zI@sNWXfiV_<;BV`_GU#{&%UbK>`2}XY@a6 zng0Lgb^n#zUvqo7YYpFfNqEjaJd%E_dvIp2fs8gF#~KRkH{g;Zu1VTo1qTb!Xi3o! zNsA>q3AV?jWVR^aNVOSB<->+j1T>*&N@7}_H zu6s?*{O13^*9~9qXRTQdX)w+x{__H+vm0W2cC5hz1R1{KS)uM2f8@AP8c*!_XxES| z|4p&i88X%Hac@%i7fRrZVWQv~U-0$l#iF_oSS5JfJ7ksbf^=?i?!bam8c*DKDdQUq z89l*H6clgSg0L=An6GLvefg6~a?e3=ZegytYAw+OI>TFmP;c;CstOnMW3B#!icC-F z_$uNDc3M}ckFRq6?n5H-c`NEWE6spN zMJUY4s0Kmk@f!817F8fx()F6EB3A00WT?6)M#;LW!_LVng%o~tL@#w%Lm8nwFf_8_ zDaU13M}*HD=@<8qnOBgr#{DrG`if>xRkMB@K%zuAu`nBBIx- z>l%3;E0Nlp2$eb}(g+Lr4aNE>)|f!4l{95_jol0jPJdR)8|ad&`&N>vv_jTSE2^+H zs#G=LPk!|pL2sJL*`*CG;SG3~r3j!M8w; zvWH8en6yD8O{WbpU=EPPRzuRj8aXnmCNao9-Eyc=)=8&~geDNRgxCxlW0P$%t_MtX zgH)f0hUi?7);7H8JEo`V%+x5XCsPclP<3L-L=^vVjhU!XS$Co8)HS(PS^uc3#EyT7 zN!tSPUV|WG_gbgDQf=@kz)M-&w~JIWE_f z)HyH57d`gkzE0;_o4P%EGr9uK;DL}7R`<_<29tLqAqE!JJ$a8P;7nO>E7jSB0T(3-!6PiH8{0KD!AlEw!q?2g9r zO*xGx%<7OVm^ZHDt7(8Q=nXo)Zz2qd;yrHwPEm+rKJeb1WE~H>SSbO8bojddtnXwc zz?lWHR75XX21Y#b{Q5d5|FvZ7L4gM+iiEQKB63JPg+w^B1BtktrcLNi(EWZw6RgvV zb8Cy6g3zk6(n3yn=|aA{rz`~<(*9nmAb^RixZCE$vj2hZ!u(2Sbm9XkuC$gHw-+18 zrdrKfU6l)}0yyHPsAT%`%EnHtEQD&l8TQt8w`28k-oAT^TT^bMfsMW7tRqM=ki@}S zo}<(fA%;8yE@nYd$z#D>%3Ocq-%mCD< zXnys%=yilTQF>4tOG31Ic#k~`>P4LBfLtCP76!nPF-5in*M1}J-Ev5q3MWE}h~a#` zIac#^(!!8_3tF`txd8-$yoMb4J&Qnt5HWzgxA60liYXP8+i+vpIX>z?Ke8^$tC{>;|TRO+JxT^Unw;2&IN?52-Rkw?~ip31slZEE+fOiKy_`& zYbPu}>cPA4B;#M_fUFSeKeN&jD+jF+f06R%BGa zLqN)m<%lltWoXWU7-pdr4T4Hm0t@QK+-;C}Oa{Rwy0Fq!EhWY#*4kf&jfTQ>@0ire zTHn{P0TR&sL@qR5_hfN)Aa($kPJOkJI5sjT0aIMjWFV*E!?rX($%4(1pKAd;EoPvg(2lO_el5P*Pa^GDa#eV@k$?0=?Ce-nT-yIBWfKu%#BwxY%AW5O zpU6=sO-j;v6CJu2IG(c8l(1h%(26dIU8N}i#77{4j`2JH7H}*+ei2Hu-e7F*&cGF~$rG`RuDO6bjk_9(z}-Yk z-NKG3DLP~czW6wHzY)Z#ecB$iL`wqxJ^x4EunqZ&ICLRG6rS5%Hpr>75i(y?kT!TB zNS_rJdS><<2-A6pNEoqxkzzkMoRNaF!~iIxd(|c6AYWFWlp+aH7PNnz>@a_A!PuOl z!IL{}E*?cX+0>ue{`h-iK<9iqAY@x`XonOK?k9Fn;<%xRSa|G=F)uH_n)O~{IlcLAoEHU z&V@BBdPIGTZ46cx{19b<7L&joOt+kSyLME{^Y^NvQ`kW!aS_ZFg@r%ib_l*JwXfaZq+ti|N3EkmN6Y zKUx@NCGifGrLaRHOU3h#=bfzYbe@S~sf&4Hvn6{EPl1os^Vo>3aiJ_&BT67h&l+5L zb*U`ROkSX`rVi|jX=1Yl)mR@mA6+B5CVF#c2r*VoF)dbCQCHGfvU3J8bj6b_&nC~P zF}gBIP#+Z|JtI9e^R#RIWHMm&y%Z^Q1K&OUxo=CQA0i;#%N1HwmkjCWVp;1xve0wUMo1sSiz%VJ5JgbTBqq>cDZ`SXn(I2z2& zNpgB9nmiP-hQdjaGWQv}7?C1qCr~SW2OKa>)4xzwo)zmT>=DH2_zRDw%AF|Pr&i7R zaT!YIEK+QUu?_6z*Jju039i&J8Vs3YzK~?E)UZBSKV)-kHUcumlu^w6CVflmcCD`9 zuj2kL$XIW-BRM^)zpu16T6*`3I z7SC&)RbzF-BGo()g?O>*PO-+M-YJEoT6Ez!)C32s+2;;Wv+54J#Hc-D#mFD>#bA&5 za@qWOg^8IJsnEd6oj&cOk|#5)A3{r`(VaZC^u}|u>Vnf)Cf;ZC<`ba={gt3@=Y#vw z5V6ly?$xpM@0xo9wy+JHnZxT;&)Z70<+58ep;uZlXhe;Bq*WzVPh;uCEmNNzW7*`B zTRox3+!PdIZA*%y_9z@iy*gm==jcNB50La1jmYnjQ+?`S@kg|pe;Q}yU%pae@uOa^ zY~x865uIHybf7af02u}9%Kw|au{EE$nE+6|%WTP0x^ zJ;dVA&J9@ht*^EGlzEOEnH3^U$zb?^$|vd^*g%TFz{z0QiA)=Y{=nt-y&s5)g(hHM zs{3Kvf%AGB0CH0{r;v8sXh4_1;!Vzh0pAWmrY~l}>MOYj0j(`z>A=uBrQulsU8a$? zgNEMzW6>2vzkFu#0Umfrf)&2 z2bvR}TRES3Hr*yyBld2amAc#(@5!q?l-XHZP_luvc@fQE8O z3+n@;e*sOVW#FPj?al^kTaaGy)ivgJFJ7m%3YOzt!T?l~3>RR(uFPFQ_ebr}Q4) z@E_dYecyNgkz0`tc#BUuzKZ+oPumzj4C$A=MAv&SR$u&Hy+e5Ye}dl#BBjX0v{}`r zGPV>=``qZAeit9p_Ya@c@)5|qmVOkdxav!x=MOnGa`u1^-aEJvLHK=Too{#G{g9(S z4+Y}oAYLKm~S@CqCTEN2iKVs*( zijoHjd2aDGmg{zqnxwA;_O`PFGr-;YBup49pt7z4Bu$u@Kn#R;p0MD|d%mU^HleUb zWY)RXjn&o`|8`GjdZz@2(7!3%gurGi>ox}NeHId?aan%d2X>OP@Zj`!*Olk@QRM(6 zIrf^>3sW#!hxds7HDc;K7F-wEYc&PC40;fp)*%cV>eiWOR;;Gp|N?vYXZo&bH^! ziSuu5DwwhTON4|kP>ti(#G>CqQ0u;yJX|A!k-Zwtvz%!F!A932M0LuWfYW;D3EqNJ zitnWR**64qlV}dLHTo>kMh$06hKCC=P}3p1r4IhMRZ-~V-7e)aBzgypC(om=S${|7 z01~M47M`KCee!2xpD5JZxtyf2@I5q8ZHe=g!5M}!vw-Q8Xs08lvVNzY93~`CXitEu znpZEuanoEDHvFsl8$ueHU7dh&V0=#XywsC1I-;KrB8PW9gKz6`(Kt1QvxgA_#jU(1 z=Vk{EqXM|UiOZX3Am26fREvX7M0tI4S;{Q4IuOY~Ef)N3%SXFsJC%U4VuVDgpnT;B zfr*u7Tk}i5$;^;b&uB=A773fhIcdU&*$2t$t1z;t4V3dll>mTeklN zIUMh#YAyp!?u$#qM4 z10I=Ag%t&*i^vaMBg^XwF;9{mD>^TQJaHZu<91ZjXkPZ7kP(Nt6i`$*Swi&qR9U5s zSO^9IcZ{&y!QTmzCn({pL{MNJtj|$wAz`P@pWZs{%EnNTkO0FzCuYzvN|X)A$W6($ zZxIs0k>?9?4I!+EaLnxp$8OJC^-rOLl^6Z=MUQlqF&|3c*Kmxy^8s@_5WUO#8CnNZ z?fJZEY9?`w16OgfWOSa85~UOLJ307-;Uk~15{UL*xg3U{e1X<;Nf8bBVWvKimCWpE z6aJ;7UP>kw6z5c+o$kTC92ljcFk8B)GQwlA(g83LCV-(R8y9ZNFX5*ZXJnMYfBumR zP>lxzE8bw_Fasjvb##z`aI^23uhNSbcs;0Npx&MC;LnZ?zD9qfytQYA6FH8{*rJZ@ zZY!N$C@M5GYNp<#u93+!phg-h9xCe}V?KY{C)n+AhXlP_5p~%C%S+8ZcG<}(&N5lx z%!DF8JXDHd=+c*`vZO{1B8{MjN2!Z~>qef3Gx`A&t*>b`7(}R?*8wT0U)ML2C#Jlz zbLPa7@q(6E`DmgcqH04Z4Sg|UPD92IG8s|1QPn2p-A3Lj9R(6bA*l(+EaCy1gCtm- zMt;iO6u>2>BVAmpyVGHOyaB6IT{%1ccEZE~VgP4})ro>CC1*2c2cN;O;f}K3;9}xO zmADo7BB}o-HiL8OTAAKieBMoU%-xdasgUKaUu0|zm47?Wr~6Y1zZk-wb(iiH4U!i0 zz%HXA^YlboB1S`ggO~83VzeLMiV#0V+Pa7t9Pi1Ly)y49bk3+?~q}#hW)g*90DP0Pr_||LwW#fu(v9)AE%+1G0X!CYwyT97{$C_W-)=pX+IT`EZsH{ z_3aDuU|T+t_OCCOe!<@W6bkSc3$Lk>I$Gnu_Vez7h3jhL@7+aD1dMfHe#MwdDYPkC z>Ztz5+!9jLWO|{0G-Xxhip(;fV9HyJBiFWP*oL3`%PhGNmwR(%f+7tpObe^ieG%qC z->||=OER;k{U2mrGf68OJ2T*r{1zXLXDG_;JY%Oc=C3M?F_~J|+2u`Lm?}qQrXol; z$+TK*opw@)X1|UwoCqG7kJQ(t2Oo|5DJVyv>I5a*{L37DbBSuKmrd^Rn@Xz|a5@iO z-f)r8nnBOlLDhq#X44}}tm`qCbC`jl6*oB}^@5T98XgQDamYAV_C{TOW65Ht(TW8> z-l1@Q6O{K=U|*0EY~Q18n_@NE#7S>*3&?4Mi+$5gc7>X6iIj(?3!~fuIxcjGb;LEP zb3k(OAf6frI7JQ-zGg>9ELv{#NiwXKy5bQ8xO|Km*1axzmkK8mnmMyxlbi90&uGqj zLx9Z2ub_TNYAHK~Hr3j+tQA|k{u`q0$a3|k0hGhd_T60oHeTmC^Y#b@`9c%Th)qLr znyOcwvELP+fQxTj&3AoHaC%mN_K2#UqeE_2*^o9X{ZoeC2y^~`PXb-%4#Bd0HWQDU zr;~35^7~f_9Kd8;+>Ft(U_*8ZAQ2m>F46hfqk`#p<5c7&`Y6a$ir7@&QE9QQTy)xD znnT!~R<+)CKhj-PY7sYX);WFZu6#Zkoz#r!xI;lP9hVX{l0|s=%wcs|&ABN^&p7@g ziBcf+y;%ao$-fucvx|Et@D_+RY|k%CS7c1C0Yg@A@b~Y>sH9$KFjJ9V^j`rJ0o9Dp z(BGC?xkLu9Q5ZQe>uasK)>y9WG;usjq#3Lj&X!;#@xpB*C92B zvq?j8N*ALA06Cx-gJ>fW67GyhssUaSrks+1%4oXvqWlr=E797XPt1vEiw*U=jKnZ{ zOR{{YK=|4rxQE8ZS=Qt6)rZEJDr>f=uIeGuRRhIYYTDmypQdA8BjYAW))5|<-CNIG zS$q7K6fqnFleEFq*1;wW`QdeI4G?P)88pQHz0`ZRQl!E)SirYhb;lRvQ-V5@wGD2p z?Jp2NG*7xA#itTX!Znzrkyg=duSRU^%VZk32>C86ub{Fay$FECt8-Sefr5qb001`va>C1&Z}|-5AunZFKL^?ecMHy+WW{kWVay_%iWn%0hag#RptlN+HTk6 zw^l0->el}-+TzS~_R->Q+B0SB7I-y?W-%x`MjoHnHu+b0Z)loFm&wlv(Qz`WO`LGM zcTk*a&fi^r2n*5*cKpZmG1)aYylShv4`rS_DZgd+N^E^G!hKqKWF|g)wD3c1cHK5@ z4_tXoFN(r@;&$|v)Y3P@J^i=fBoD<@wrDw*t01 znU3U~KDZ$q{EdjKh9i@nh0(5c9NnfYaGJ5)GPGW|y~aPii{P~+_7-jfc45{{ZhGXZ zyhu}!op(j%zR+sH=l;MaB25MFH9CrY=7s!1ffn;f|0aOS;JFoQL9(`yT-*Sc7{M=W z`#eMn_>=(t5yLHs1Dq;Gyqg0q#c?U%+dT2Mc>d4JOpcs)dGbdFtVe2gpWkkQ+7 zmFD}OxWaOHIRpsV!GUN8U?LkuW5lBM?WPib49nSJKF~g?Qi9|Hj?@V2h9VmFnfTXx z0^f*Dala�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

}Z=a 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 z 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 1) { + if (args[0].toLowerCase().endsWith("delete")) { + final File jarOld = new File(args[1]); + if (jarOld.exists()) { + if (!jarOld.delete()) { + jarOld.deleteOnExit(); + } + } + } + } + } + + public static void extractResources() { + final ArrayList extract = new ArrayList(2); + if (Configuration.getCurrentOperatingSystem() == Configuration.OperatingSystem.WINDOWS) { + extract.add(Configuration.Paths.COMPILE_SCRIPTS_BAT); + extract.add(Configuration.Paths.COMPILE_FIND_JDK); + } else { + extract.add(Configuration.Paths.COMPILE_SCRIPTS_SH); + } + for (final String item : extract) { + final String path = Configuration.Paths.Resources.ROOT + "/" + item; + final InputStream in; + try { + in = Configuration.getResourceURL(path).openStream(); + } catch (IOException ignored) { + continue; + } + final File output = new File(Configuration.Paths.getHomeDirectory(), item); + IOHelper.write(in, output); + } + } + + /** + * Returns the Bot for any object loaded in its client. For internal use + * only (not useful for script writers). + * + * @param o Any object from within the client. + * @return The Bot for the client. + */ + public static Bot getBot(final Object o) { + return gui.getBot(o); + } + + /** + * Returns the size of the panel that clients should be drawn into. For + * internal use. + * + * @return The client panel size. + */ + public static Dimension getPanelSize() { + return gui.getPanel().getSize(); + } + + private static void bootstrap() { + Logger.getLogger("").setLevel(Level.ALL); + Logger.getLogger("").addHandler(new SystemConsoleHandler()); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + private final Logger log = Logger.getLogger("EXCEPTION"); + + public void uncaughtException(final Thread t, final Throwable e) { + log.logp(Level.SEVERE, "EXCEPTION", "", "Unhandled exception in thread " + t.getName() + ": ", e); + } + }); + System.setErr(new PrintStream(new LogOutputStream(Logger.getLogger("STDERR"), Level.SEVERE), true)); + } +} diff --git a/src/org/rsbot/Boot.java b/src/org/rsbot/Boot.java new file mode 100644 index 0000000..993e9cb --- /dev/null +++ b/src/org/rsbot/Boot.java @@ -0,0 +1,71 @@ +package org.rsbot; + +import java.io.IOException; +import java.net.URLDecoder; + + +/** + * @author Paris + */ +public class Boot { + public static void main(final String[] args) throws IOException { + String location = Boot.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + location = URLDecoder.decode(location, "UTF-8").replaceAll("\\\\", "/"); + final String app = Application.class.getCanonicalName(); + final String flags = "-Xmx512m -Dsun.java2d.d3d=false -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+AggressiveOpts -XX:+UseBiasedLocking"; + boolean sh = true; + final char q = '"', s = ' '; + final StringBuilder param = new StringBuilder(64); + + switch (Configuration.getCurrentOperatingSystem()) { + case WINDOWS: + sh = false; + param.append("javaw"); + param.append(s); + param.append(flags); + break; + case MAC: + param.append("java"); + param.append(s); + param.append(flags); + param.append(s); + param.append("-Xdock:name="); + param.append(q); + param.append(Configuration.NAME); + param.append(q); + param.append(s); + param.append("-Xdock:icon="); + param.append(q); + param.append(Configuration.Paths.Resources.ICON); + param.append(q); + break; + default: + param.append("java"); + param.append(s); + param.append(flags); + break; + } + + param.append(s); + param.append("-classpath"); + param.append(s); + param.append(q); + param.append(location); + param.append(q); + param.append(s); + param.append(app); + + for (final String arg : args) { + param.append(s); + param.append(arg); + } + + final Runtime run = Runtime.getRuntime(); + + if (sh) { + run.exec(new String[]{"/bin/sh", "-c", param.toString()}); + } else { + run.exec(param.toString()); + } + } +} diff --git a/src/org/rsbot/Configuration.java b/src/org/rsbot/Configuration.java new file mode 100644 index 0000000..9cf66c7 --- /dev/null +++ b/src/org/rsbot/Configuration.java @@ -0,0 +1,342 @@ +package org.rsbot; + +import org.rsbot.log.LogFormatter; +import org.rsbot.log.SystemConsoleHandler; +import org.rsbot.log.TextAreaLogHandler; + +import javax.swing.filechooser.FileSystemView; +import java.awt.*; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Properties; +import java.util.logging.FileHandler; +import java.util.logging.LogManager; + +public class Configuration { + public enum OperatingSystem { + MAC, WINDOWS, LINUX, UNKNOWN + } + + public static class Paths { + public static class Resources { + public static final String ROOT = "resources"; + public static final String SCRIPTS = Paths.SCRIPTS_NAME_SRC + "/"; + public static final String ROOT_IMG = ROOT + "/images"; + public static final String ICON = ROOT_IMG + "/icon.png"; + public static final String ICON_APPADD = ROOT_IMG + "/application_add.png"; + public static final String ICON_APPDELETE = ROOT_IMG + "/application_delete.png"; + public static final String ICON_ARROWIN = ROOT_IMG + "/arrow_in.png"; + public static final String ICON_REFRESH = ROOT_IMG + "/arrow_refresh.png"; + public static final String ICON_DELETE = ROOT_IMG + "/delete.png"; + public static final String ICON_GITHUB = ROOT_IMG + "/github.png"; + public static final String ICON_PLAY = ROOT_IMG + "/control_play_blue.png"; + public static final String ICON_PAUSE = ROOT_IMG + "/control_pause.png"; + public static final String DATABASE_ERROR = ROOT_IMG + "/database_error.png"; + public static final String ICON_ADD = ROOT_IMG + "/add.png"; + public static final String ICON_HOME = ROOT_IMG + "/home.png"; + public static final String ICON_BOT = ROOT_IMG + "/bot.png"; + public static final String ICON_CLOSE = ROOT_IMG + "/close.png"; + public static final String ICON_TICK = ROOT_IMG + "/tick.png"; + public static final String ICON_MOUSE = ROOT_IMG + "/mouse.png"; + public static final String ICON_PHOTO = ROOT_IMG + "/photo.png"; + public static final String ICON_REPORTKEY = ROOT_IMG + "/report_key.png"; + public static final String ICON_REPORT_DISK = ROOT_IMG + "/report_disk.png"; + public static final String ICON_INFO = ROOT_IMG + "/information.png"; + public static final String ICON_KEY = ROOT_IMG + "/key.png"; + public static final String ICON_KEYBOARD = ROOT_IMG + "/keyboard.png"; + public static final String ICON_CONNECT = ROOT_IMG + "/connect.png"; + public static final String ICON_DISCONNECT = ROOT_IMG + "/disconnect.png"; + public static final String ICON_START = ROOT_IMG + "/control_play.png"; + public static final String ICON_SCRIPT = ROOT_IMG + "/script.png"; + public static final String ICON_SCRIPT_ADD = ROOT_IMG + "/script_add.png"; + public static final String ICON_SCRIPT_LIVE = ROOT_IMG + "/script_lightning.png"; + public static final String ICON_SCRIPT_GEAR = ROOT_IMG + "/script_gear.png"; + public static final String ICON_SCRIPT_EDIT = ROOT_IMG + "/script_edit.png"; + public static final String ICON_WEBLINK = ROOT_IMG + "/world_link.png"; + public static final String ICON_WRENCH = ROOT_IMG + "/wrench.png"; + + public static final String VERSION = ROOT + "/version.txt"; + } + + public static class URLs { + public static final String HOST = "powerbot.org"; + private static final String BASE = "http://links." + HOST + "/"; + public static final String DOWNLOAD = BASE + "download"; + public static final String UPDATE = BASE + "modscript"; + public static final String VERSION = BASE + "version.txt"; + public static final String VERSION_KILL = BASE + "version-kill"; + public static final String PROJECT = BASE + "git-project"; + public static final String SITE = BASE + "site"; + public static final String SDN_CONTROL = BASE + "sdn-control"; + public static final String AD_INFO = BASE + "botad-info"; + public static final String MONITORING_CONTROL = BASE + "monitoring"; + public static final String WEBCOMPILER = BASE + "webcompile"; + public static final String SERVICELOGIN = BASE + "servicelogin"; + } + + public static final String ROOT = new File(".").getAbsolutePath(); + + public static final String COMPILE_SCRIPTS_BAT = "Compile-Scripts.bat"; + public static final String COMPILE_SCRIPTS_SH = "compile-scripts.sh"; + public static final String COMPILE_FIND_JDK = "FindJDK.bat"; + + public static final String SCRIPTS_NAME_SRC = "scripts"; + public static final String SCRIPTS_NAME_OUT = "Scripts"; + + public static String getAccountsFile() { + final String path; + if (Configuration.getCurrentOperatingSystem() == OperatingSystem.WINDOWS) { + path = System.getenv("APPDATA") + File.separator + Configuration.NAME + "_Accounts.ini"; + } else { + path = Paths.getUnixHome() + File.separator + "." + Configuration.NAME_LOWERCASE + "acct"; + } + return path; + } + + public static String getHomeDirectory() { + final String env = System.getenv(Configuration.NAME.toUpperCase() + "_HOME"); + if (env == null || env.isEmpty()) { + return (Configuration.getCurrentOperatingSystem() == OperatingSystem.WINDOWS ? + FileSystemView.getFileSystemView().getDefaultDirectory().getAbsolutePath() : + Paths.getUnixHome()) + File.separator + Configuration.NAME; + } else { + return env; + } + } + + public static String getLogsDirectory() { + return Paths.getHomeDirectory() + File.separator + "Logs"; + } + + public static String getPathCache() { + return Paths.getSettingsDirectory() + File.separator + "path.txt"; + } + + public static String getUIDsFile() { + return Paths.getSettingsDirectory() + File.separator + "uid.txt"; + } + + public static String getScreenshotsDirectory() { + return Paths.getHomeDirectory() + File.separator + "Screenshots"; + } + + public static String getScriptsDirectory() { + return Paths.getHomeDirectory() + File.separator + Paths.SCRIPTS_NAME_OUT; + } + + public static String getScriptsSourcesDirectory() { + return Paths.getScriptsDirectory() + File.separator + "Sources"; + } + + public static String getScriptsPrecompiledDirectory() { + return Paths.getScriptsDirectory() + File.separator + "Precompiled"; + } + + public static String getScriptsNetworkDirectory() { + return Paths.getScriptsDirectory() + File.separator + "Network"; + } + + public static String getCacheDirectory() { + return Paths.getHomeDirectory() + File.separator + "Cache"; + } + + public static String getScriptCacheDirectory() { + return getCacheDirectory() + File.separator + "Scripts"; + } + + public static String getVersionCache() { + return Paths.getCacheDirectory() + File.separator + "info.dat"; + } + + public static String getWebDatabase() { + return Paths.getSettingsDirectory() + File.separator + "Web.store"; + } + + public static String getServiceKey() { + return Paths.getSettingsDirectory() + File.separator + "service.key"; + } + + public static String getSettingsDirectory() { + return Paths.getHomeDirectory() + File.separator + "Settings"; + } + + public static String getGarbageDirectory() { + final File dir = new File(Configuration.Paths.getScriptCacheDirectory(), ".java"); + if (!dir.exists()) { + dir.mkdirs(); + } + String path = dir.getAbsolutePath(); + try { + path = URLDecoder.decode(path, "UTF-8"); + } catch (final UnsupportedEncodingException ignored) { + } + return path; + } + + public static String getRunningJarPath() { + if (!RUNNING_FROM_JAR) { + return null; + } + String path = new File(Configuration.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath(); + try { + path = URLDecoder.decode(path, "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } + return path; + } + + public static String getUnixHome() { + final String home = System.getProperty("user.home"); + return home == null ? "~" : home; + } + } + + public static final String NAME = "RSBot"; + public static final String NAME_LOWERCASE = NAME.toLowerCase(); + private static final OperatingSystem CURRENT_OS; + public static boolean RUNNING_FROM_JAR = false; + + public static class Twitter { + public static final boolean ENABLED = true; + public static final String NAME = "rsbotorg"; + public static final String HASHTAG = "#" + NAME_LOWERCASE; + public static final int MESSAGES = 3; + } + + static { + final URL resource = Configuration.class.getClassLoader().getResource(Paths.Resources.VERSION); + if (resource != null) { + Configuration.RUNNING_FROM_JAR = true; + } + final String os = System.getProperty("os.name"); + if (os.contains("Mac")) { + CURRENT_OS = OperatingSystem.MAC; + } else if (os.contains("Windows")) { + CURRENT_OS = OperatingSystem.WINDOWS; + } else if (os.contains("Linux")) { + CURRENT_OS = OperatingSystem.LINUX; + } else { + CURRENT_OS = OperatingSystem.UNKNOWN; + } + final ArrayList dirs = new ArrayList(); + dirs.add(Paths.getHomeDirectory()); + dirs.add(Paths.getLogsDirectory()); + dirs.add(Paths.getCacheDirectory()); + dirs.add(Paths.getSettingsDirectory()); + dirs.add(Paths.getScriptsDirectory()); + dirs.add(Paths.getScriptsSourcesDirectory()); + dirs.add(Paths.getScriptsPrecompiledDirectory()); + for (final String name : dirs) { + final File dir = new File(name); + if (!dir.exists()) { + dir.mkdirs(); + } + } + final Properties logging = new Properties(); + final String logFormatter = LogFormatter.class.getCanonicalName(); + final String fileHandler = FileHandler.class.getCanonicalName(); + logging.setProperty("handlers", TextAreaLogHandler.class.getCanonicalName() + "," + fileHandler); + logging.setProperty(".level", "INFO"); + logging.setProperty(SystemConsoleHandler.class.getCanonicalName() + ".formatter", logFormatter); + logging.setProperty(fileHandler + ".formatter", logFormatter); + logging.setProperty(TextAreaLogHandler.class.getCanonicalName() + ".formatter", logFormatter); + logging.setProperty(fileHandler + ".pattern", Paths.getLogsDirectory() + File.separator + "%u.%g.log"); + logging.setProperty(fileHandler + ".count", "10"); + final ByteArrayOutputStream logout = new ByteArrayOutputStream(); + try { + logging.store(logout, ""); + LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(logout.toByteArray())); + } catch (final Exception ignored) { + } + if (Configuration.RUNNING_FROM_JAR) { + String path = resource.toString(); + try { + path = URLDecoder.decode(path, "UTF-8"); + } catch (final UnsupportedEncodingException ignored) { + } + final String prefix = "jar:file:/"; + if (path.indexOf(prefix) == 0) { + path = path.substring(prefix.length()); + path = path.substring(0, path.indexOf('!')); + if (File.separatorChar != '/') { + path = path.replace('/', File.separatorChar); + } + try { + final File pathfile = new File(Paths.getPathCache()); + if (pathfile.exists()) { + pathfile.delete(); + } + pathfile.createNewFile(); + final Writer out = new BufferedWriter(new FileWriter(Paths.getPathCache())); + out.write(path); + out.close(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + } + } + + public static URL getResourceURL(final String path) throws MalformedURLException { + return RUNNING_FROM_JAR ? Configuration.class.getResource("/" + path) : new File(path).toURI().toURL(); + } + + public static Image getImage(final String resource) { + try { + return Toolkit.getDefaultToolkit().getImage(getResourceURL(resource)); + } catch (final Exception e) { + } + return null; + } + + public static OperatingSystem getCurrentOperatingSystem() { + return Configuration.CURRENT_OS; + } + + public static int getVersion() { + InputStreamReader is = null; + BufferedReader reader = null; + try { + is = new InputStreamReader(RUNNING_FROM_JAR ? + Configuration.class.getClassLoader().getResourceAsStream( + Paths.Resources.VERSION) : new FileInputStream(Paths.Resources.VERSION)); + reader = new BufferedReader(is); + final String s = reader.readLine().trim(); + return Integer.parseInt(s); + } catch (final Exception e) { + } finally { + try { + if (is != null) { + is.close(); + } + if (reader != null) { + reader.close(); + } + } catch (final IOException ioe) { + } + } + return -1; + } + + public static String getVersionFormatted() { + return getVersionFormatted(getVersion()); + } + + public static String getVersionFormatted(final int version) { + final float v = (float) version / 100; + String s = Float.toString(v); + final int z = s.indexOf('.'); + if (z == -1) { + s += ".00"; + } else { + final String exp = s.substring(z + 1); + if (exp.length() == 1) { + s += "0"; + } + } + return s; + } +} \ No newline at end of file diff --git a/src/org/rsbot/bot/Bot.java b/src/org/rsbot/bot/Bot.java new file mode 100644 index 0000000..df82cbc --- /dev/null +++ b/src/org/rsbot/bot/Bot.java @@ -0,0 +1,274 @@ +package org.rsbot.bot; + +import org.rsbot.Application; +import org.rsbot.client.Client; +import org.rsbot.client.input.Canvas; +import org.rsbot.event.EventManager; +import org.rsbot.event.events.PaintEvent; +import org.rsbot.event.events.TextPaintEvent; +import org.rsbot.gui.AccountManager; +import org.rsbot.script.background.BankMonitor; +import org.rsbot.script.background.WebData; +import org.rsbot.script.background.WebLoader; +import org.rsbot.script.internal.BackgroundScriptHandler; +import org.rsbot.script.internal.BreakHandler; +import org.rsbot.script.internal.InputManager; +import org.rsbot.script.internal.ScriptHandler; +import org.rsbot.script.methods.Environment; +import org.rsbot.script.methods.MethodContext; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.lang.reflect.Constructor; +import java.util.EventListener; +import java.util.Map; +import java.util.TreeMap; + +public class Bot { + private String account; + private BotStub botStub; + private Client client; + private MethodContext methods; + private Component panel; + private final PaintEvent paintEvent; + private final TextPaintEvent textPaintEvent; + private final EventManager eventManager; + private BufferedImage backBuffer; + private BufferedImage image; + private final InputManager im; + private RSLoader loader; + private final ScriptHandler sh; + private final BackgroundScriptHandler bsh; + private final BreakHandler bh; + private final Map listeners; + private boolean killBackground = false; + + /** + * Whether or not user input is allowed despite a script's preference. + */ + public volatile boolean overrideInput = false; + + /** + * Whether or not all anti-randoms are enabled. + */ + public volatile boolean disableRandoms = false; + + /** + * Whether or not the login screen anti-random is enabled. + */ + public volatile boolean disableAutoLogin = false; + + /** + * Whether or not rendering is enabled. + */ + public volatile boolean disableRendering = false; + + /** + * Defines what types of input are enabled when overrideInput is false. + * Defaults to 'keyboard only' whenever a script is started. + */ + public volatile int inputFlags = Environment.INPUT_KEYBOARD | Environment.INPUT_MOUSE; + + public Bot() { + im = new InputManager(this); + loader = new RSLoader(); + final Dimension size = Application.getPanelSize(); + loader.setCallback(new Runnable() { + public void run() { + try { + setClient((Client) loader.getClient()); + resize(size.width, size.height); + methods.menu.setupListener(); + } catch (final Exception ignored) { + } + } + }); + sh = new ScriptHandler(this); + bsh = new BackgroundScriptHandler(this); + bh = new BreakHandler(this); + backBuffer = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB); + image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB); + paintEvent = new PaintEvent(); + textPaintEvent = new TextPaintEvent(); + eventManager = new EventManager(); + listeners = new TreeMap(); + } + + public void start() { + try { + loader.paint(image.getGraphics()); + loader.load(); + if (loader.getTargetName() == null) { + return; + } + botStub = new BotStub(loader); + loader.setStub(botStub); + eventManager.start(); + botStub.setActive(true); + final ThreadGroup tg = new ThreadGroup("RSClient-" + hashCode()); + final Thread thread = new Thread(tg, loader, "Loader"); + thread.start(); + new Thread() { + @Override + public void run() { + try { + while (methods == null && !killBackground) { + Thread.sleep(50); + } + } catch (final InterruptedException ignored) { + } + if (methods != null && !killBackground) { + bsh.runScript(new WebData()); + bsh.runScript(new WebLoader()); + bsh.runScript(new BankMonitor()); + } + } + }.start(); + } catch (final Exception ignored) { + } + } + + public void stop() { + eventManager.killThread(false); + sh.stopScript(); + bsh.stopScript(); + loader.stop(); + loader.destroy(); + killBackground = true; + loader = null; + } + + public void resize(final int width, final int height) { + backBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // client reads size of loader applet for drawing + loader.setSize(width, height); + // simulate loader repaint awt event dispatch + final Graphics g = backBuffer.getGraphics(); + loader.update(g); + loader.paint(g); + } + + public boolean setAccount(final String name) { + boolean exist = false; + for (final String s : AccountManager.getAccountNames()) { + if (s.toLowerCase().equals(name.toLowerCase())) { + exist = true; + } + } + if (exist) { + account = name; + return true; + } + account = null; + return false; + } + + public void setPanel(final Component c) { + panel = c; + } + + public void addListener(final Class clazz) { + final EventListener el = instantiateListener(clazz); + listeners.put(clazz.getName(), el); + eventManager.addListener(el); + } + + public void removeListener(final Class clazz) { + final EventListener el = listeners.get(clazz.getName()); + listeners.remove(clazz.getName()); + eventManager.removeListener(el); + } + + public boolean hasListener(final Class clazz) { + return clazz != null && listeners.get(clazz.getName()) != null; + } + + public String getAccountName() { + return account; + } + + public Client getClient() { + return client; + } + + public Canvas getCanvas() { + if (client == null) { + return null; + } + return (Canvas) client.getCanvas(); + } + + public Graphics getBufferGraphics() { + final Graphics back = backBuffer.getGraphics(); + paintEvent.graphics = back; + textPaintEvent.graphics = back; + textPaintEvent.idx = 0; + eventManager.processEvent(paintEvent); + eventManager.processEvent(textPaintEvent); + back.dispose(); + image.getGraphics().drawImage(backBuffer, 0, 0, null); + if (panel != null) { + panel.repaint(); + } + return backBuffer.getGraphics(); + } + + public BufferedImage getImage() { + return image; + } + + public BotStub getBotStub() { + return botStub; + } + + public RSLoader getLoader() { + return loader; + } + + public MethodContext getMethodContext() { + return methods; + } + + public EventManager getEventManager() { + return eventManager; + } + + public InputManager getInputManager() { + return im; + } + + public BreakHandler getBreakHandler() { + return bh; + } + + public ScriptHandler getScriptHandler() { + return sh; + } + + public BackgroundScriptHandler getBackgroundScriptHandler() { + return bsh; + } + + private void setClient(final Client cl) { + client = cl; + client.setCallback(new CallbackImpl(this)); + methods = new MethodContext(this); + sh.init(); + } + + private EventListener instantiateListener(final Class clazz) { + try { + EventListener listener; + try { + final Constructor constructor = clazz.getConstructor(Bot.class); + listener = (EventListener) constructor.newInstance(this); + } catch (final Exception e) { + listener = clazz.asSubclass(EventListener.class).newInstance(); + } + return listener; + } catch (final Exception ignored) { + } + return null; + } +} \ No newline at end of file diff --git a/src/org/rsbot/bot/BotStub.java b/src/org/rsbot/bot/BotStub.java new file mode 100644 index 0000000..d620694 --- /dev/null +++ b/src/org/rsbot/bot/BotStub.java @@ -0,0 +1,143 @@ +package org.rsbot.bot; + +import org.rsbot.Configuration; + +import javax.swing.*; +import java.applet.Applet; +import java.applet.AppletContext; +import java.applet.AppletStub; +import java.applet.AudioClip; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.logging.Logger; + +public class BotStub implements AppletStub, AppletContext { + private final Map> IMAGE_CACHE = new HashMap>(); + private final Map INPUT_CACHE = Collections.synchronizedMap( + new HashMap(2)); + + private final Logger log = Logger.getLogger(BotStub.class.getName()); + private final Applet applet; + private final URL codeBase; + private final URL documentBase; + private boolean isActive; + private final Map parameters; + + public BotStub(final RSLoader applet) { + this.applet = applet; + final Crawler c = new Crawler("http://www." + applet.getTargetName() + ".com/"); + parameters = c.getParameters(); + final String world_prefix = c.getWorldPrefix(); + try { + codeBase = new URL("http://world" + world_prefix + "." + applet.getTargetName() + ".com"); + documentBase = new URL("http://world" + world_prefix + "." + applet.getTargetName() + ".com/m0"); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public void appletResize(final int x, final int y) { + final Dimension size = new Dimension(x, y); + applet.setSize(size); + applet.setPreferredSize(size); + } + + public Applet getApplet(final String name) { + final String thisName = parameters.get("name"); + if (thisName == null) { + return null; + } + return thisName.equals(name) ? applet : null; + } + + public AppletContext getAppletContext() { + return this; + } + + public Enumeration getApplets() { + final Vector apps = new Vector(); + apps.add(applet); + return apps.elements(); + } + + public AudioClip getAudioClip(final URL url) { + throw new UnsupportedOperationException("NOT YET IMPLEMENTED getAudioClip=" + url); + } + + public URL getCodeBase() { + return codeBase; + } + + public URL getDocumentBase() { + return documentBase; + } + + public Image getImage(final URL url) { + synchronized (IMAGE_CACHE) { + WeakReference ref = IMAGE_CACHE.get(url); + Image img; + if (ref == null || (img = ref.get()) == null) { + img = Toolkit.getDefaultToolkit().createImage(url); + ref = new WeakReference(img); + IMAGE_CACHE.put(url, ref); + } + return img; + } + } + + public String getParameter(final String s) { + final String parameter = parameters.get(s); + if (s != null) { + return parameter; + } + return ""; + } + + public InputStream getStream(final String key) { + return INPUT_CACHE.get(key); + } + + public Iterator getStreamKeys() { + return Collections.unmodifiableSet(INPUT_CACHE.keySet()).iterator(); + } + + public boolean isActive() { + return isActive; + } + + public void setActive(final boolean isActive) { + this.isActive = isActive; + } + + public void setStream(final String key, final InputStream stream) throws IOException { + INPUT_CACHE.put(key, stream); + } + + public void showDocument(final URL url) { + showDocument(url, ""); + } + + public void showDocument(final URL url, final String target) { + if (url.toString().contains("outofdate")) { + final String message = Configuration.NAME + " is currently outdated, please wait patiently for a new version."; + log.severe(message); + JOptionPane.showMessageDialog(null, message, "Outdated", JOptionPane.WARNING_MESSAGE); + final File versionFile = new File(Configuration.Paths.getVersionCache()); + if (versionFile.exists() && !versionFile.delete()) { + log.warning("Unable to clear cache."); + } + } else if (!target.equals("tbi")) { + log.info("Attempting to show: " + url.toString() + " [" + target + "]"); + } + } + + public void showStatus(final String status) { + log.info("Status: " + status); + } +} \ No newline at end of file diff --git a/src/org/rsbot/bot/CallbackImpl.java b/src/org/rsbot/bot/CallbackImpl.java new file mode 100644 index 0000000..9db38b2 --- /dev/null +++ b/src/org/rsbot/bot/CallbackImpl.java @@ -0,0 +1,38 @@ +package org.rsbot.bot; + +import org.rsbot.client.Callback; +import org.rsbot.client.Render; +import org.rsbot.client.RenderData; +import org.rsbot.event.events.CharacterMovedEvent; +import org.rsbot.event.events.MessageEvent; +import org.rsbot.script.methods.MethodContext; + +public class CallbackImpl implements Callback { + + private final Bot bot; + + public CallbackImpl(final Bot bot) { + this.bot = bot; + } + + public Bot getBot() { + return bot; + } + + public void notifyMessage(final int id, final String sender, final String msg) { + final MessageEvent m = new MessageEvent(sender, id, msg); + bot.getEventManager().dispatchEvent(m); + } + + public void rsCharacterMoved(final org.rsbot.client.RSCharacter c, final int i) { + final CharacterMovedEvent e = new CharacterMovedEvent(bot.getMethodContext(), c, i); + bot.getEventManager().dispatchEvent(e); + } + + public void updateRenderInfo(final Render r, final RenderData rd) { + final MethodContext ctx = bot.getMethodContext(); + if (ctx != null) { + ctx.calc.updateRenderInfo(r, rd); + } + } +} diff --git a/src/org/rsbot/bot/Crawler.java b/src/org/rsbot/bot/Crawler.java new file mode 100644 index 0000000..15ec255 --- /dev/null +++ b/src/org/rsbot/bot/Crawler.java @@ -0,0 +1,96 @@ +package org.rsbot.bot; + +import org.rsbot.util.io.HttpClient; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class Crawler { + private static final Logger log = Logger.getLogger(Crawler.class.getName()); + + private static HashMap parameters; + private final String world_prefix; + + public Crawler(final String root) { + final String index = firstMatch("Continue to Full Site for News and Game Help", + downloadPage(root, null)); + + final String frame = root + "game.ws"; + + final String game = firstMatch("]*)\"?>", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + final Matcher matcher = pattern.matcher(downloadPage(game, frame)); + parameters = new HashMap(); + while (matcher.find()) { + final String key = removeTrailingChar(matcher.group(1), '"'); + final String value = removeTrailingChar(matcher.group(2), '"'); + if (!parameters.containsKey(key)) { + parameters.put(key, value); + } + } + + final String ie = "haveie6"; + if (parameters.containsKey(ie)) { + parameters.remove(ie); + } + parameters.put("haveie6", "0"); + + log.fine("Parameters: " + parameters); + } + + private String downloadPage(final String url, final String referer) { + try { + final HttpURLConnection con = HttpClient.getHttpConnection(new URL(url)); + if (referer != null && !referer.isEmpty()) { + con.addRequestProperty("Referer", referer); + } + return HttpClient.downloadAsString(con); + } catch (final IOException e) { + e.printStackTrace(); + return ""; + } + } + + private String firstMatch(final String regex, final String str) { + final Pattern pattern = Pattern.compile(regex); + final Matcher matcher = pattern.matcher(str); + while (matcher.find()) { + return matcher.group(1); + } + return null; + } + + public HashMap getParameters() { + return parameters; + } + + public String getWorldPrefix() { + return world_prefix; + } + + private String removeTrailingChar(final String str, final char ch) { + if (str == null || str.isEmpty()) { + return str; + } else if (str.length() == 1) { + return str.charAt(0) == ch ? "" : str; + } + try { + final int l = str.length() - 1; + if (str.charAt(l) == ch) { + return str.substring(0, l); + } + return str; + } catch (final Exception e) { + return str; + } + } +} diff --git a/src/org/rsbot/bot/RSClassLoader.java b/src/org/rsbot/bot/RSClassLoader.java new file mode 100644 index 0000000..3d6875d --- /dev/null +++ b/src/org/rsbot/bot/RSClassLoader.java @@ -0,0 +1,107 @@ +package org.rsbot.bot; + +import java.awt.*; +import java.io.*; +import java.net.SocketPermission; +import java.net.URL; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.Permissions; +import java.security.ProtectionDomain; +import java.util.Calendar; +import java.util.Map; +import java.util.PropertyPermission; + +/** + * @author Alex + */ +public final class RSClassLoader extends ClassLoader { + + private Map classes; + private ProtectionDomain domain; + + public RSClassLoader(final Map classes, final URL source) { + try { + final CodeSource codeSource = new CodeSource(source, (CodeSigner[]) null); + domain = new ProtectionDomain(codeSource, getPermissions()); + this.classes = classes; + + //Get path of org/rsbot/client/RandomAccessFile + String s = getClass().getResource("RSClassLoader.class").toString(); + s = s.replace("bot/RSClassLoader.class", "client/RandomAccessFile.class"); + final URL url = new URL(s); + + //Read org/rsbot/client/RandomAccessFile + InputStream is = null; + try { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(5000); + is = new BufferedInputStream(url.openStream()); + + final byte[] buff = new byte[1024]; + int len; + while ((len = is.read(buff)) != -1) { + bos.write(buff, 0, len); + } + + final byte[] data = bos.toByteArray(); + + //Store it so we can load it + this.classes.put("org.rsbot.client.RandomAccessFile", data); + } catch (final IOException e) { + e.printStackTrace(); + } finally { + if (is != null) { + is.close(); + } + } + } catch (final Exception ignored) { + } + } + + private Permissions getPermissions() { + final Permissions ps = new Permissions(); + ps.add(new AWTPermission("accessEventQueue")); + ps.add(new PropertyPermission("user.home", "read")); + ps.add(new PropertyPermission("java.vendor", "read")); + ps.add(new PropertyPermission("java.version", "read")); + ps.add(new PropertyPermission("os.name", "read")); + ps.add(new PropertyPermission("os.arch", "read")); + ps.add(new PropertyPermission("os.version", "read")); + ps.add(new SocketPermission("*", "connect,resolve")); + String uDir = System.getProperty("user.home"); + if (uDir != null) { + uDir += "/"; + } else { + uDir = "~/"; + } + final String[] dirs = {"c:/rscache/", "/rscache/", "c:/windows/", "c:/winnt/", "c:/", uDir, "/tmp/", "."}; + final String[] rsDirs = {".jagex_cache_32", ".file_store_32"}; + for (String dir : dirs) { + final File f = new File(dir); + ps.add(new FilePermission(dir, "read")); + if (!f.exists()) { + continue; + } + dir = f.getPath(); + for (final String rsDir : rsDirs) { + ps.add(new FilePermission(dir + File.separator + rsDir + File.separator + "-", "read")); + ps.add(new FilePermission(dir + File.separator + rsDir + File.separator + "-", "write")); + } + } + Calendar.getInstance(); + //TimeZone.getDefault();//Now the default is set they don't need permission + //ps.add(new FilePermission()) + ps.setReadOnly(); + return ps; + } + + @Override + public final Class loadClass(final String name) throws ClassNotFoundException { + if (classes.containsKey(name)) { + final byte buffer[] = classes.remove(name); + return defineClass(name, buffer, 0, buffer.length, domain); + } + return super.loadClass(name); + } + +} diff --git a/src/org/rsbot/bot/RSLoader.java b/src/org/rsbot/bot/RSLoader.java new file mode 100644 index 0000000..3be9ac5 --- /dev/null +++ b/src/org/rsbot/bot/RSLoader.java @@ -0,0 +1,178 @@ +package org.rsbot.bot; + +import org.rsbot.Application; +import org.rsbot.Configuration; +import org.rsbot.client.Loader; +import org.rsbot.loader.ClientLoader; +import org.rsbot.loader.script.ParseException; + +import java.applet.Applet; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Qauters + */ +public class RSLoader extends Applet implements Runnable, Loader { + private final Logger log = Logger.getLogger(RSLoader.class.getName()); + + private static final long serialVersionUID = 6288499508495040201L; + + /** + * The applet of the client + */ + private Applet client; + + private Runnable loadedCallback; + + private String targetName; + + private Dimension size = Application.getPanelSize(); + + /** + * The game class loader + */ + private RSClassLoader classLoader; + + @Override + public final synchronized void destroy() { + if (client != null) { + client.destroy(); + } + } + + @Override + public boolean isShowing() { + return true; + } + + @Override + public final synchronized void init() { + if (client != null) { + client.init(); + } + } + + @Override + public final void paint(final Graphics graphics) { + if (client != null) { + client.paint(graphics); + } else { + final Font font = new Font("Helvetica", 1, 13); + final FontMetrics fontMetrics = getFontMetrics(font); + graphics.setColor(Color.black); + graphics.fillRect(0, 0, 768, 503); + graphics.setColor(new Color(150, 0, 0)); + graphics.drawRect(230, 233, 304, 34); + final String s = "Loading..."; + graphics.setFont(font); + graphics.setColor(Color.WHITE); + graphics.drawString(s, (768 - fontMetrics.stringWidth(s)) / 2, 255); + } + } + + /** + * The run void of the loader + */ + public void run() { + try { + final Class c = classLoader.loadClass("client"); + client = (Applet) c.newInstance(); + loadedCallback.run(); + c.getMethod("provideLoaderApplet", new Class[]{java.applet.Applet.class}).invoke(null, this); + client.init(); + client.start(); + } catch (final Throwable e) { + log.severe("Unable to load client, please check your firewall and internet connection."); + final File versionFile = new File(Configuration.Paths.getVersionCache()); + if (versionFile.exists() && !versionFile.delete()) { + log.warning("Unable to clear cache."); + } + + log.log(Level.SEVERE, "Error reason:", e); + } + } + + public Applet getClient() { + return client; + } + + public void load() { + final File ms = new File(Configuration.Paths.getCacheDirectory(), "ms.dat"); + try { + final ClientLoader cl = new ClientLoader(); + cl.init(new URL(Configuration.Paths.URLs.UPDATE), ms); + final File client = new File(Configuration.Paths.getCacheDirectory(), "client.dat"); + cl.load(client, new File(Configuration.Paths.getVersionCache())); + targetName = cl.getTargetName(); + classLoader = new RSClassLoader(cl.getClasses(), new URL("http://" + targetName + ".com/")); + } catch (final IOException ex) { + log.severe("Unable to load client: " + ex.getMessage()); + } catch (final ParseException ex) { + log.info("Unable to load client: " + ex.toString()); + if (ms.exists()) { + ms.delete(); + } + log.severe("Cached objects deleted, please try restarting the application"); + } + } + + public void setCallback(final Runnable r) { + loadedCallback = r; + } + + public String getTargetName() { + return targetName; + } + + /** + * Overridden void start() + */ + @Override + public final synchronized void start() { + if (client != null) { + client.start(); + } + } + + /** + * Overridden void deactivate() + */ + @Override + public final synchronized void stop() { + if (client != null) { + client.stop(); + } + } + + /** + * Overridden void update(Graphics) + */ + @Override + public final void update(final Graphics graphics) { + if (client != null) { + client.update(graphics); + } else { + paint(graphics); + } + } + + @Override + public final void setSize(final int width, final int height) { + super.setSize(width, height); + size = new Dimension(width, height); + } + + @Override + public final Dimension getSize() { + return size; + } + + public RSClassLoader getClassLoader() { + return classLoader; + } +} diff --git a/src/org/rsbot/client/Cache.java b/src/org/rsbot/client/Cache.java new file mode 100644 index 0000000..ec201fd --- /dev/null +++ b/src/org/rsbot/client/Cache.java @@ -0,0 +1,12 @@ +package org.rsbot.client; + +public interface Cache { + + HashTable getTable(); + + int getInitialCount(); + + int getSpaceLeft(); + + NodeSubQueue getList(); +} diff --git a/src/org/rsbot/client/Callback.java b/src/org/rsbot/client/Callback.java new file mode 100644 index 0000000..cfb61ad --- /dev/null +++ b/src/org/rsbot/client/Callback.java @@ -0,0 +1,14 @@ +package org.rsbot.client; + +import org.rsbot.bot.Bot; + +public interface Callback { + + public Bot getBot(); + + public void notifyMessage(int id, String sender, String msg); + + public void rsCharacterMoved(RSCharacter c, int i); + + public void updateRenderInfo(Render r, RenderData rd); +} diff --git a/src/org/rsbot/client/ChatLine.java b/src/org/rsbot/client/ChatLine.java new file mode 100644 index 0000000..bcf5006 --- /dev/null +++ b/src/org/rsbot/client/ChatLine.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface ChatLine { + + public String getName(); + + public String getMessage(); + +} diff --git a/src/org/rsbot/client/Client.java b/src/org/rsbot/client/Client.java new file mode 100644 index 0000000..dbc8b5c --- /dev/null +++ b/src/org/rsbot/client/Client.java @@ -0,0 +1,156 @@ +package org.rsbot.client; + +import org.rsbot.client.input.Keyboard; +import org.rsbot.client.input.Mouse; + +import java.awt.*; + +public interface Client { + + ChatLine[] getChatLines(); + + boolean isMenuCollapsed(); + + NodeDeque getMenuItems(); + + NodeSubQueue getCollapsedMenuItems(); + + int getBaseX(); + + int getBaseY(); + + Callback getCallBack(); + + Canvas getCanvas(); + + int getCameraPitch(); + + int getCameraYaw(); + + int getCamPosX(); + + int getCamPosY(); + + int getCamPosZ(); + + String getCurrentPassword(); + + String getCurrentUsername(); + + int getDestX(); + + int getDestY(); + + DetailInfoNode getDetailInfoNode(); + + byte[][][] getGroundByteArray(); + + int getGUIRSInterfaceIndex(); + + int getIdleTime(); + + Keyboard getKeyboard(); + + int getLoginIndex(); + + int getLoopCycle(); + + int getMenuOptionsCount(); + + int getMenuX(); + + int getMenuY(); + + MenuGroupNode getCurrentMenuGroupNode(); + + int getSubMenuX(); + + int getSubMenuY(); + + int getSubMenuWidth(); + + int getMinimapAngle(); + + float getMinimapOffset(); + + int getMinimapScale(); + + int getMinimapSetting(); + + Mouse getMouse(); + + //MouseWheel getMouseWheel(); + + RSPlayer getMyRSPlayer(); + + int getPlane(); + + int getPublicChatMode(); + + RSGround[][][] getRSGroundArray(); + + RSGroundData[] getRSGroundDataArray(); + + StatusNodeList getRSInteractingDefList(); + + Rectangle[] getRSInterfaceBoundsArray(); + + RSInterface[][] getRSInterfaceCache(); + + HashTable getRSInterfaceNC(); + + HashTable getRSItemHashTable(); + + HashTable getRSNPCNC(); + + int getRSNPCCount(); + + int[] getRSNPCIndexArray(); + + RSPlayer[] getRSPlayerArray(); + + int getRSPlayerCount(); + + int[] getRSPlayerIndexArray(); + + String getSelectedItemName(); + + int getSelfInteracting(); + + Settings getSettingArray(); + + Signlink getSignLink(); + + int[] getSkillExperiences(); + + int[] getSkillExperiencesMax(); + + int[] getSkillLevelMaxes(); + + int[] getSkillLevels(); + + TileData[] getTileData(); + + boolean[] getValidRSInterfaceArray(); + + boolean isFlagged(); + + int isItemSelected(); + + boolean isMenuOpen(); + + boolean isSpellSelected(); + + RSItemDefLoader getRSItemDefLoader(); + + RSObjectDefLoader getRSObjectDefLoader(); + + StatusNodeListLoader getRSInteractableDefListLoader(); + + Signlink getSignlink(); + + ServerData getWorldData(); + + void setCallback(Callback cb); + +} diff --git a/src/org/rsbot/client/DefLoader.java b/src/org/rsbot/client/DefLoader.java new file mode 100644 index 0000000..b5b17a5 --- /dev/null +++ b/src/org/rsbot/client/DefLoader.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface DefLoader { + + Cache getCache(); + +} diff --git a/src/org/rsbot/client/DetailInfo.java b/src/org/rsbot/client/DetailInfo.java new file mode 100644 index 0000000..57b89d7 --- /dev/null +++ b/src/org/rsbot/client/DetailInfo.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface DetailInfo { + int getDetailLevel(); +} diff --git a/src/org/rsbot/client/DetailInfoNode.java b/src/org/rsbot/client/DetailInfoNode.java new file mode 100644 index 0000000..babb878 --- /dev/null +++ b/src/org/rsbot/client/DetailInfoNode.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface DetailInfoNode extends Node { + public DetailInfo getDetailInfo(); +} diff --git a/src/org/rsbot/client/Graphic.java b/src/org/rsbot/client/Graphic.java new file mode 100644 index 0000000..4621d58 --- /dev/null +++ b/src/org/rsbot/client/Graphic.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface Graphic { + + int getID(); + +} \ No newline at end of file diff --git a/src/org/rsbot/client/HDModel.java b/src/org/rsbot/client/HDModel.java new file mode 100644 index 0000000..fc4073f --- /dev/null +++ b/src/org/rsbot/client/HDModel.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface HDModel extends Model { + +} diff --git a/src/org/rsbot/client/HardReference.java b/src/org/rsbot/client/HardReference.java new file mode 100644 index 0000000..bb8fe2e --- /dev/null +++ b/src/org/rsbot/client/HardReference.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface HardReference extends Reference { + + Object get(); + +} diff --git a/src/org/rsbot/client/HashTable.java b/src/org/rsbot/client/HashTable.java new file mode 100644 index 0000000..2feea5f --- /dev/null +++ b/src/org/rsbot/client/HashTable.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface HashTable { + + Node[] getBuckets(); + +} diff --git a/src/org/rsbot/client/LDModel.java b/src/org/rsbot/client/LDModel.java new file mode 100644 index 0000000..4dfde51 --- /dev/null +++ b/src/org/rsbot/client/LDModel.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface LDModel extends Model { + +} diff --git a/src/org/rsbot/client/Loader.java b/src/org/rsbot/client/Loader.java new file mode 100644 index 0000000..332ef38 --- /dev/null +++ b/src/org/rsbot/client/Loader.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +import java.applet.Applet; + +public interface Loader { + public Applet getClient(); +} diff --git a/src/org/rsbot/client/MenuGroupNode.java b/src/org/rsbot/client/MenuGroupNode.java new file mode 100644 index 0000000..069541f --- /dev/null +++ b/src/org/rsbot/client/MenuGroupNode.java @@ -0,0 +1,14 @@ +package org.rsbot.client; + +/** + * @author Jacmob + */ +public interface MenuGroupNode extends NodeSub { + + NodeSubQueue getItems(); + + String getOption(); + + int size(); + +} diff --git a/src/org/rsbot/client/MenuItemNode.java b/src/org/rsbot/client/MenuItemNode.java new file mode 100644 index 0000000..811d7a7 --- /dev/null +++ b/src/org/rsbot/client/MenuItemNode.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface MenuItemNode extends NodeSub { + + String getAction(); + + String getOption(); + + int getType(); + +} diff --git a/src/org/rsbot/client/Model.java b/src/org/rsbot/client/Model.java new file mode 100644 index 0000000..e0b7f93 --- /dev/null +++ b/src/org/rsbot/client/Model.java @@ -0,0 +1,15 @@ +package org.rsbot.client; + +public interface Model { + public int[] getXPoints(); + + public int[] getYPoints(); + + public int[] getZPoints(); + + public short[] getIndices1(); + + public short[] getIndices2(); + + public short[] getIndices3(); +} diff --git a/src/org/rsbot/client/ModelCapture.java b/src/org/rsbot/client/ModelCapture.java new file mode 100644 index 0000000..55869e4 --- /dev/null +++ b/src/org/rsbot/client/ModelCapture.java @@ -0,0 +1,72 @@ +package org.rsbot.client; + +import java.util.Arrays; + +/** + * Implementation of the Model interface used to + * store model data by injection in transform + * methods where model implementations in the + * client are reused. + * + * @author Jacmob + */ +public class ModelCapture implements Model { + + private int[] vertex_x; + private int[] vertex_y; + private int[] vertex_z; + + private short[] face_a; + private short[] face_b; + private short[] face_c; + + public ModelCapture(final Model model) { + if (model == null) { + return; + } + int[] vertices = model.getXPoints(); + vertex_x = Arrays.copyOf(vertices, vertices.length); + vertices = model.getYPoints(); + vertex_y = Arrays.copyOf(vertices, vertices.length); + vertices = model.getZPoints(); + vertex_z = Arrays.copyOf(vertices, vertices.length); + + short[] faces = model.getIndices1(); + face_a = Arrays.copyOf(faces, faces.length); + faces = model.getIndices2(); + face_b = Arrays.copyOf(faces, faces.length); + faces = model.getIndices3(); + face_c = Arrays.copyOf(faces, faces.length); + } + + @Override + public int[] getXPoints() { + return vertex_x; + } + + @Override + public int[] getYPoints() { + return vertex_y; + } + + @Override + public int[] getZPoints() { + return vertex_z; + } + + @Override + public short[] getIndices1() { + return face_a; + } + + @Override + public short[] getIndices2() { + return face_b; + } + + @Override + public short[] getIndices3() { + return face_c; + } + +} diff --git a/src/org/rsbot/client/Node.java b/src/org/rsbot/client/Node.java new file mode 100644 index 0000000..c4cc5ce --- /dev/null +++ b/src/org/rsbot/client/Node.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface Node { + + long getID(); + + Node getNext(); + + Node getPrevious(); + +} diff --git a/src/org/rsbot/client/NodeDeque.java b/src/org/rsbot/client/NodeDeque.java new file mode 100644 index 0000000..261f1c7 --- /dev/null +++ b/src/org/rsbot/client/NodeDeque.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface NodeDeque { + + Node getCurrent(); + + Node getTail(); + +} diff --git a/src/org/rsbot/client/NodeListCache.java b/src/org/rsbot/client/NodeListCache.java new file mode 100644 index 0000000..95ce6a6 --- /dev/null +++ b/src/org/rsbot/client/NodeListCache.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface NodeListCache { + + NodeDeque getNodeList(); + +} diff --git a/src/org/rsbot/client/NodeSub.java b/src/org/rsbot/client/NodeSub.java new file mode 100644 index 0000000..86cca9d --- /dev/null +++ b/src/org/rsbot/client/NodeSub.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface NodeSub extends Node { + + NodeSub getNextSub(); + + NodeSub getPrevSub(); + +} diff --git a/src/org/rsbot/client/NodeSubQueue.java b/src/org/rsbot/client/NodeSubQueue.java new file mode 100644 index 0000000..f86207c --- /dev/null +++ b/src/org/rsbot/client/NodeSubQueue.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface NodeSubQueue { + + NodeSub getCurrent(); + + NodeSub getTail(); + +} diff --git a/src/org/rsbot/client/RSAnimable.java b/src/org/rsbot/client/RSAnimable.java new file mode 100644 index 0000000..3a525dc --- /dev/null +++ b/src/org/rsbot/client/RSAnimable.java @@ -0,0 +1,13 @@ +package org.rsbot.client; + +public interface RSAnimable extends RSInteractable { + + short getX1(); + + short getX2(); + + short getY1(); + + short getY2(); + +} diff --git a/src/org/rsbot/client/RSAnimableNode.java b/src/org/rsbot/client/RSAnimableNode.java new file mode 100644 index 0000000..23f1706 --- /dev/null +++ b/src/org/rsbot/client/RSAnimableNode.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface RSAnimableNode { + + RSAnimableNode getNext(); + + RSAnimable getRSAnimable(); + +} diff --git a/src/org/rsbot/client/RSCharacter.java b/src/org/rsbot/client/RSCharacter.java new file mode 100644 index 0000000..fd99786 --- /dev/null +++ b/src/org/rsbot/client/RSCharacter.java @@ -0,0 +1,29 @@ +package org.rsbot.client; + +public interface RSCharacter extends RSAnimable { + + int getAnimation(); + + Graphic[] getGraphicsData(); + + int getHeight(); + + int getHPRatio(); + + int getInteracting(); + + int[] getLocationX(); + + int[] getLocationY(); + + int getOrientation(); + + int getLoopCycleStatus(); + + String getMessage(); + + int isMoving(); + + Model getModel(); + +} diff --git a/src/org/rsbot/client/RSGround.java b/src/org/rsbot/client/RSGround.java new file mode 100644 index 0000000..28db096 --- /dev/null +++ b/src/org/rsbot/client/RSGround.java @@ -0,0 +1,27 @@ +package org.rsbot.client; + +public interface RSGround { + + byte getPlane1(); + + byte getPlane2(); + + RSAnimableNode getRSAnimableList(); + + RSInteractable getFloorDecoration(); + + RSInteractable getBoundary1(); + + RSInteractable getBoundary2(); + + RSInteractable getWallDecoration1(); + + RSInteractable getWallDecoration2(); + + RSGroundEntity getGroundObject(); + + short getX(); + + short getY(); + +} diff --git a/src/org/rsbot/client/RSGroundData.java b/src/org/rsbot/client/RSGroundData.java new file mode 100644 index 0000000..0416c6a --- /dev/null +++ b/src/org/rsbot/client/RSGroundData.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface RSGroundData { + + int[][] getBlocks(); + + int getX(); + + int getY(); + +} diff --git a/src/org/rsbot/client/RSGroundEntity.java b/src/org/rsbot/client/RSGroundEntity.java new file mode 100644 index 0000000..2575e71 --- /dev/null +++ b/src/org/rsbot/client/RSGroundEntity.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +/** + * @author Jacmob + */ +public interface RSGroundEntity extends RSAnimable { +} diff --git a/src/org/rsbot/client/RSGroundObject.java b/src/org/rsbot/client/RSGroundObject.java new file mode 100644 index 0000000..08c010d --- /dev/null +++ b/src/org/rsbot/client/RSGroundObject.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface RSGroundObject extends RSGroundEntity { + + int getID(); + + int getStackSize(); + + Model getModel(); + +} diff --git a/src/org/rsbot/client/RSInteractable.java b/src/org/rsbot/client/RSInteractable.java new file mode 100644 index 0000000..da28546 --- /dev/null +++ b/src/org/rsbot/client/RSInteractable.java @@ -0,0 +1,10 @@ +package org.rsbot.client; + +public interface RSInteractable { + + int getX(); + + int getY(); + + byte getPlane(); +} diff --git a/src/org/rsbot/client/RSInteractableDef.java b/src/org/rsbot/client/RSInteractableDef.java new file mode 100644 index 0000000..440209c --- /dev/null +++ b/src/org/rsbot/client/RSInteractableDef.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface RSInteractableDef extends StatusNode { + + RSInteractable getRSInteractable(); + +} diff --git a/src/org/rsbot/client/RSInterface.java b/src/org/rsbot/client/RSInterface.java new file mode 100644 index 0000000..9cb4293 --- /dev/null +++ b/src/org/rsbot/client/RSInterface.java @@ -0,0 +1,99 @@ +package org.rsbot.client; + +public interface RSInterface { + + String[] getActions(); + + int getBorderThickness(); + + int getBoundsArrayIndex(); + + int getComponentID(); + + int getComponentIndex(); + + String getComponentName(); + + RSInterface[] getComponents(); + + int getComponentStackSize(); + + int getHeight(); + + int getHeight2(); + + int getHorizontalScrollBarSize(); + + int getHorizontalScrollBarThumbPosition(); + + int getHorizontalScrollBarThumbSize(); + + int getID(); + + int[] getInventory(); + + int[] getInventoryStackSizes(); + + int getInvSpritePadX(); + + int getInvSpritePadY(); + + int getMasterX(); + + int getMasterY(); + + int getModelID(); + + int getModelType(); + + int getModelZoom(); + + int getParentID(); + + String getSelectedActionName(); + + int getShadowColor(); + + int getSpecialType(); + + String getSpellName(); + + String getText(); + + int getTextColor(); + + int getTextureID(); + + String getToolTip(); + + int getType(); + + int[][] getValueIndexArray(); + + int getVerticalScrollBarPosition(); + + int getVerticalScrollBarSize(); + + int getVerticalScrollBarThumbSize(); + + int getWidth(); + + int getWidth2(); + + int getX(); + + int getXRotation(); + + int getY(); + + int getYRotation(); + + int getZRotation(); + + boolean isHorizontallyFlipped(); + + boolean isInventoryRSInterface(); + + boolean isVerticallyFlipped(); + +} diff --git a/src/org/rsbot/client/RSInterfaceNode.java b/src/org/rsbot/client/RSInterfaceNode.java new file mode 100644 index 0000000..945172a --- /dev/null +++ b/src/org/rsbot/client/RSInterfaceNode.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface RSInterfaceNode extends Node { + + int getMainID(); + +} diff --git a/src/org/rsbot/client/RSItem.java b/src/org/rsbot/client/RSItem.java new file mode 100644 index 0000000..ae6f4eb --- /dev/null +++ b/src/org/rsbot/client/RSItem.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface RSItem { + + int getID(); + + int getStackSize(); + +} diff --git a/src/org/rsbot/client/RSItemDef.java b/src/org/rsbot/client/RSItemDef.java new file mode 100644 index 0000000..7a1949e --- /dev/null +++ b/src/org/rsbot/client/RSItemDef.java @@ -0,0 +1,20 @@ +package org.rsbot.client; + +public interface RSItemDef { + + String[] getActions(); + + int getCertID(); + + int getCertTemplateID(); + + String[] getGroundActions(); + + int getID(); + + String getName(); + + boolean isMembersObject(); + + RSItemDefLoader getLoader(); +} diff --git a/src/org/rsbot/client/RSItemDefLoader.java b/src/org/rsbot/client/RSItemDefLoader.java new file mode 100644 index 0000000..797d522 --- /dev/null +++ b/src/org/rsbot/client/RSItemDefLoader.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface RSItemDefLoader extends DefLoader { + + boolean isMembers(); + +} diff --git a/src/org/rsbot/client/RSNPC.java b/src/org/rsbot/client/RSNPC.java new file mode 100644 index 0000000..e805d80 --- /dev/null +++ b/src/org/rsbot/client/RSNPC.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface RSNPC extends RSCharacter { + + RSNPCDef getRSNPCDef(); + + int getLevel(); + +} diff --git a/src/org/rsbot/client/RSNPCDef.java b/src/org/rsbot/client/RSNPCDef.java new file mode 100644 index 0000000..fb24bf1 --- /dev/null +++ b/src/org/rsbot/client/RSNPCDef.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface RSNPCDef { + + String[] getActions(); + + String getName(); + + int getType(); + +} diff --git a/src/org/rsbot/client/RSNPCDefLoader.java b/src/org/rsbot/client/RSNPCDefLoader.java new file mode 100644 index 0000000..0a7ae2e --- /dev/null +++ b/src/org/rsbot/client/RSNPCDefLoader.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface RSNPCDefLoader extends DefLoader { + +} diff --git a/src/org/rsbot/client/RSNPCNode.java b/src/org/rsbot/client/RSNPCNode.java new file mode 100644 index 0000000..0f2258d --- /dev/null +++ b/src/org/rsbot/client/RSNPCNode.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface RSNPCNode extends Node { + public RSNPC getRSNPC(); +} diff --git a/src/org/rsbot/client/RSObject.java b/src/org/rsbot/client/RSObject.java new file mode 100644 index 0000000..3dc240b --- /dev/null +++ b/src/org/rsbot/client/RSObject.java @@ -0,0 +1,15 @@ +package org.rsbot.client; + +public interface RSObject { + + int getID(); + + int getType(); + + int getX(); + + int getY(); + + Model getModel(); + +} diff --git a/src/org/rsbot/client/RSObjectComposite.java b/src/org/rsbot/client/RSObjectComposite.java new file mode 100644 index 0000000..5a3c0be --- /dev/null +++ b/src/org/rsbot/client/RSObjectComposite.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface RSObjectComposite { + +} diff --git a/src/org/rsbot/client/RSObjectDef.java b/src/org/rsbot/client/RSObjectDef.java new file mode 100644 index 0000000..967b14e --- /dev/null +++ b/src/org/rsbot/client/RSObjectDef.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +public interface RSObjectDef { + + String[] getActions(); + + int[] getChildrenIDs(); + + String getName(); + +} diff --git a/src/org/rsbot/client/RSObjectDefLoader.java b/src/org/rsbot/client/RSObjectDefLoader.java new file mode 100644 index 0000000..b360f01 --- /dev/null +++ b/src/org/rsbot/client/RSObjectDefLoader.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface RSObjectDefLoader extends DefLoader { + +} diff --git a/src/org/rsbot/client/RSPlayer.java b/src/org/rsbot/client/RSPlayer.java new file mode 100644 index 0000000..51dcb72 --- /dev/null +++ b/src/org/rsbot/client/RSPlayer.java @@ -0,0 +1,13 @@ +package org.rsbot.client; + +public interface RSPlayer extends RSCharacter { + + int getLevel(); + + String getName(); + + int getTeam(); + + RSPlayerComposite getComposite(); + +} diff --git a/src/org/rsbot/client/RSPlayerComposite.java b/src/org/rsbot/client/RSPlayerComposite.java new file mode 100644 index 0000000..23aa04b --- /dev/null +++ b/src/org/rsbot/client/RSPlayerComposite.java @@ -0,0 +1,10 @@ +package org.rsbot.client; + +/** + * @author Jacmob + */ +public interface RSPlayerComposite { + + int getNPCID(); + +} diff --git a/src/org/rsbot/client/RandomAccessFile.java b/src/org/rsbot/client/RandomAccessFile.java new file mode 100644 index 0000000..1215f03 --- /dev/null +++ b/src/org/rsbot/client/RandomAccessFile.java @@ -0,0 +1,208 @@ +package org.rsbot.client; + +import org.rsbot.Application; +import org.rsbot.bot.Bot; +import org.rsbot.util.io.PreferenceData; +import org.rsbot.util.io.UIDData; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class RandomAccessFile { + private UIDData uidData = null; + private PreferenceData prefData = null; + private java.io.RandomAccessFile raf = null; + private Client client = null; + + private byte[] data = null; + private int offset = 0; + + public RandomAccessFile(final String name, final String mode) throws FileNotFoundException { + if (!shouldOverride(name, mode)) { + raf = new java.io.RandomAccessFile(name, mode); + } + } + + public RandomAccessFile(final File file, final String mode) throws FileNotFoundException { + if (!shouldOverride(file.getName(), mode)) { + raf = new java.io.RandomAccessFile(file, mode); + } + } + + private boolean shouldOverride(final String filename, final String mode) throws FileNotFoundException { + if (filename.equals("random.dat")) { + uidData = new UIDData(); + } else if (filename.endsWith("preferences.dat")) { + prefData = new PreferenceData(1); + } else if (filename.endsWith("preferences2.dat")) { + prefData = new PreferenceData(2); + } else if (filename.endsWith("preferences3.dat")) { + prefData = new PreferenceData(3); + } else { + return false; + } + + return true; + } + + private void checkData() { + if (uidData != null) { + if (client == null) { + final Bot b = Application.getBot(this); + client = b.getClient(); + } + final String accountName = client != null ? client.getCurrentUsername() : ""; + + if (!uidData.getLastUsed().equals(accountName) && data != null) { + uidData.setUID(uidData.getLastUsed(), data); + data = uidData.getUID(accountName); + offset = 0; + } else if (data == null) { + data = uidData.getUID(accountName); + offset = 0; + } + } else if (prefData != null && data == null) { + data = prefData.get(); + } + } + + private void saveData() { + if (uidData != null && data != null) { + uidData.setUID(client != null ? client.getCurrentUsername() : "", data); + uidData.save(); + } else if (prefData != null && data != null) { + prefData.set(data); + } + } + + public void close() throws IOException { + if (raf != null) { + raf.close(); + } + } + + public long length() throws IOException { + checkData(); + + if (data != null) { + return data.length; + } + + return raf.length(); + } + + public int read() throws IOException { + try { + checkData(); + + if (data != null) { + if (data.length <= offset) { + return -1; + } + + return 0xFF & data[offset++]; + } + + return raf.read(); + } catch (final Exception e) { + e.printStackTrace(); + } + + return -1; + } + + public int read(final byte[] b, final int off, int len) throws IOException { + checkData(); + + if (data != null) { + try { + if (b.length < off + len) { + len = b.length - off; + } + + if (data.length < offset + len) { + len = data.length - offset; + } + + if (len <= 0) { + return -1; + } + + for (int i = 0; i < len; i++) { + b[off + i] = data[offset++]; + } + + return len; + } catch (final Exception e) { + e.printStackTrace(); + } + } + + return raf.read(b, off, len); + } + + public void seek(final long pos) throws IOException { + checkData(); + + if (pos < 0) { + throw new IOException("pos < 0"); + } + + if (data != null) { + offset = (int) pos; + } else { + raf.seek(pos); + } + } + + public void write(final byte[] b, final int off, int len) throws IOException { + checkData(); + + if (data != null) { + //Check arguments + if (b.length < off + len) { + len = b.length - off; + } + + if (len <= 0) { + return; + } + + //Increase buffer if needed + if (data.length < offset + len) { + final byte[] tmp = data; + data = new byte[offset + len]; + System.arraycopy(tmp, 0, data, 0, (offset <= tmp.length ? offset : tmp.length)); + } + + //Write bytes + for (int i = 0; i < len; i++) { + data[offset++] = b[off + i]; + } + + saveData(); + } else { + raf.write(b, off, len); + } + } + + public void write(final int b) throws IOException { + checkData(); + + if (data != null) { + //Increase bufer if needed + if (data.length < offset + 1) { + final byte[] tmp = data; + data = new byte[offset + 1]; + System.arraycopy(tmp, 0, data, 0, (offset <= tmp.length ? offset : tmp.length)); + } + + //Write byte + data[offset++] = (byte) b; + saveData(); + } else { + raf.write(b); + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/client/Reference.java b/src/org/rsbot/client/Reference.java new file mode 100644 index 0000000..c51e719 --- /dev/null +++ b/src/org/rsbot/client/Reference.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface Reference extends NodeSub { + + public int getIndex(); + +} diff --git a/src/org/rsbot/client/Render.java b/src/org/rsbot/client/Render.java new file mode 100644 index 0000000..1d368e9 --- /dev/null +++ b/src/org/rsbot/client/Render.java @@ -0,0 +1,24 @@ +package org.rsbot.client; + +/** + * GraphicsToolkit + */ +public interface Render { + + int getAbsoluteX1(); + + int getAbsoluteX2(); + + int getAbsoluteY1(); + + int getAbsoluteY2(); + + int getXMultiplier(); + + int getYMultiplier(); + + int getZFar(); + + int getZNear(); + +} diff --git a/src/org/rsbot/client/RenderData.java b/src/org/rsbot/client/RenderData.java new file mode 100644 index 0000000..3c958fc --- /dev/null +++ b/src/org/rsbot/client/RenderData.java @@ -0,0 +1,35 @@ +package org.rsbot.client; + +/** + * Viewport + */ +public interface RenderData { + + // x calculation values + float getXOff(); + + float getXX(); + + float getXY(); + + float getXZ(); + + // y calculation values + float getYOff(); + + float getYX(); + + float getYY(); + + float getYZ(); + + // z calculation values + float getZOff(); + + float getZX(); + + float getZY(); + + float getZZ(); + +} diff --git a/src/org/rsbot/client/ServerData.java b/src/org/rsbot/client/ServerData.java new file mode 100644 index 0000000..7b45f17 --- /dev/null +++ b/src/org/rsbot/client/ServerData.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface ServerData { + + public int getWorldID(); + +} diff --git a/src/org/rsbot/client/Settings.java b/src/org/rsbot/client/Settings.java new file mode 100644 index 0000000..13471a7 --- /dev/null +++ b/src/org/rsbot/client/Settings.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface Settings { + + public int[] getData(); + +} diff --git a/src/org/rsbot/client/Signlink.java b/src/org/rsbot/client/Signlink.java new file mode 100644 index 0000000..30e9d09 --- /dev/null +++ b/src/org/rsbot/client/Signlink.java @@ -0,0 +1,11 @@ +package org.rsbot.client; + +import java.applet.Applet; +import java.awt.*; + +public interface Signlink { + + public EventQueue getEventQueue(); + + public Applet getGameApplet(); +} diff --git a/src/org/rsbot/client/SoftReference.java b/src/org/rsbot/client/SoftReference.java new file mode 100644 index 0000000..d564bd2 --- /dev/null +++ b/src/org/rsbot/client/SoftReference.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface SoftReference extends Reference { + + java.lang.ref.SoftReference getReference(); + +} diff --git a/src/org/rsbot/client/StatusNode.java b/src/org/rsbot/client/StatusNode.java new file mode 100644 index 0000000..bdca021 --- /dev/null +++ b/src/org/rsbot/client/StatusNode.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface StatusNode { + + StatusNode getNext(); + + StatusNode getPrevious(); + +} diff --git a/src/org/rsbot/client/StatusNodeList.java b/src/org/rsbot/client/StatusNodeList.java new file mode 100644 index 0000000..625b65b --- /dev/null +++ b/src/org/rsbot/client/StatusNodeList.java @@ -0,0 +1,9 @@ +package org.rsbot.client; + +public interface StatusNodeList { + + StatusNode getHead(); + + StatusNode getNext(); + +} diff --git a/src/org/rsbot/client/StatusNodeListLoader.java b/src/org/rsbot/client/StatusNodeListLoader.java new file mode 100644 index 0000000..4b6fcf7 --- /dev/null +++ b/src/org/rsbot/client/StatusNodeListLoader.java @@ -0,0 +1,5 @@ +package org.rsbot.client; + +public interface StatusNodeListLoader { + public StatusNodeList getList(); +} diff --git a/src/org/rsbot/client/TileData.java b/src/org/rsbot/client/TileData.java new file mode 100644 index 0000000..6150475 --- /dev/null +++ b/src/org/rsbot/client/TileData.java @@ -0,0 +1,7 @@ +package org.rsbot.client; + +public interface TileData { + + int[][] getHeights(); + +} diff --git a/src/org/rsbot/client/input/Canvas.java b/src/org/rsbot/client/input/Canvas.java new file mode 100644 index 0000000..0cfa44f --- /dev/null +++ b/src/org/rsbot/client/input/Canvas.java @@ -0,0 +1,126 @@ +package org.rsbot.client.input; + +import org.rsbot.Application; +import org.rsbot.bot.Bot; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.image.*; +import java.util.Hashtable; + +public class Canvas extends java.awt.Canvas { + public static final int GRAPHICS_DELAY = 6; + public static final int SLOW_GRAPHICS_DELAY = 50; + + private static final long serialVersionUID = -2276037172265300477L; + + private Bot bot; + private boolean toshi; + + private boolean visible; + private boolean focused; + + public Canvas() { + init(); + } + + public Canvas(final GraphicsConfiguration c) { + super(c); + init(); + } + + @Override + public final Graphics getGraphics() { + if (bot == null) { + if (toshi) { + return super.getGraphics(); + } else { + bot = Application.getBot(this); + toshi = true; + } + } + try { + Thread.sleep(bot.disableRendering ? SLOW_GRAPHICS_DELAY : GRAPHICS_DELAY); + } catch (final InterruptedException ignored) { + } + return bot.getBufferGraphics(); + } + + @Override + public final boolean hasFocus() { + return focused; + } + + @Override + public final boolean isValid() { + return visible; + } + + @Override + public final boolean isVisible() { + return visible; + } + + @Override + public final boolean isDisplayable() { + return true; + } + + @Override + public final Dimension getSize() { + if (bot != null) { + return bot.getLoader().getSize(); + } + return Application.getPanelSize(); + } + + @Override + public final void setVisible(final boolean visible) { + super.setVisible(visible); + this.visible = visible; + } + + public final void setFocused(final boolean focused) { + if (focused && !this.focused) { + // null opposite; permanent gain, as expected when entire Applet + // regains focus + super.processEvent(new FocusEvent(this, FocusEvent.FOCUS_GAINED, false, null)); + } else if (this.focused) { + // null opposite; temporary loss, as expected when entire Applet + // loses focus + super.processEvent(new FocusEvent(this, FocusEvent.FOCUS_LOST, true, null)); + } + this.focused = focused; + } + + @SuppressWarnings("rawtypes") + @Override + public Image createImage(final int width, final int height) { + // Prevents NullPointerException when opening world map. + // This is caused by the character loader, which creates + // character sprites using this method (which will return + // null as long as this canvas is not really displayed). + final int[] pixels = new int[height * width]; + final DataBufferInt databufferint = new DataBufferInt(pixels, pixels.length); + final DirectColorModel directcolormodel = new DirectColorModel(32, 0xff0000, 0xff00, 255); + final WritableRaster writableraster = Raster.createWritableRaster(directcolormodel.createCompatibleSampleModel(width, height), databufferint, null); + return new BufferedImage(directcolormodel, writableraster, false, new Hashtable()); + } + + @Override + protected final void processEvent(final AWTEvent e) { + if (!(e instanceof FocusEvent)) { + super.processEvent(e); + } + } + + private void init() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + setFocused(true); + } + }); + } +} \ No newline at end of file diff --git a/src/org/rsbot/client/input/Focus.java b/src/org/rsbot/client/input/Focus.java new file mode 100644 index 0000000..36331b5 --- /dev/null +++ b/src/org/rsbot/client/input/Focus.java @@ -0,0 +1,20 @@ +package org.rsbot.client.input; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +public abstract class Focus implements FocusListener { + public abstract void _focusGained(FocusEvent e); + + public abstract void _focusLost(FocusEvent e); + + @Override + public final void focusGained(final FocusEvent e) { + _focusGained(e); + } + + @Override + public final void focusLost(final FocusEvent e) { + _focusLost(e); + } +} diff --git a/src/org/rsbot/client/input/Keyboard.java b/src/org/rsbot/client/input/Keyboard.java new file mode 100644 index 0000000..d27d2cf --- /dev/null +++ b/src/org/rsbot/client/input/Keyboard.java @@ -0,0 +1,33 @@ +package org.rsbot.client.input; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +/** + * @author Alex + */ +public abstract class Keyboard extends Focus implements KeyListener { + public abstract void _keyPressed(KeyEvent e); + + public abstract void _keyReleased(KeyEvent e); + + public abstract void _keyTyped(KeyEvent e); + + @Override + public void keyPressed(final KeyEvent e) { + // System.out.println(("KP"); + _keyPressed(e); + } + + @Override + public void keyReleased(final KeyEvent e) { + // System.out.println(("KR"); + _keyReleased(e); + } + + @Override + public void keyTyped(final KeyEvent e) { + // System.out.println(("KT"); + _keyTyped(e); + } +} diff --git a/src/org/rsbot/client/input/Mouse.java b/src/org/rsbot/client/input/Mouse.java new file mode 100644 index 0000000..8a6aa70 --- /dev/null +++ b/src/org/rsbot/client/input/Mouse.java @@ -0,0 +1,168 @@ +package org.rsbot.client.input; + +import java.awt.*; +import java.awt.event.*; + +public abstract class Mouse extends Focus implements MouseListener, + MouseMotionListener, MouseWheelListener { + + private int clientX; + private int clientY; + private int clientPressX = -1; + private int clientPressY = -1; + private long clientPressTime = -1; + private boolean clientPresent; + private boolean clientPressed; + + public abstract void _mouseClicked(MouseEvent e); + + public abstract void _mouseDragged(MouseEvent e); + + public abstract void _mouseEntered(MouseEvent e); + + public abstract void _mouseExited(MouseEvent e); + + public abstract void _mouseMoved(MouseEvent e); + + public abstract void _mousePressed(MouseEvent e); + + public abstract void _mouseReleased(MouseEvent e); + + public abstract void _mouseWheelMoved(MouseWheelEvent e); + + public abstract Component getComponent(); + + public int getX() { + return clientX; + } + + public int getY() { + return clientY; + } + + public int getPressX() { + return clientPressX; + } + + public int getPressY() { + return clientPressY; + } + + public long getPressTime() { + return clientPressTime; + } + + public boolean isPressed() { + return clientPressed; + } + + public boolean isPresent() { + return clientPresent; + } + + public final void mouseClicked(final MouseEvent e) { + clientX = e.getX(); + clientY = e.getY(); + _mouseClicked(e); + e.consume(); + } + + public final void mouseDragged(final MouseEvent e) { + clientX = e.getX(); + clientY = e.getY(); + _mouseDragged(e); + e.consume(); + } + + public final void mouseEntered(final MouseEvent e) { + clientPresent = true; + clientX = e.getX(); + clientY = e.getY(); + _mouseEntered(e); + e.consume(); + } + + public final void mouseExited(final MouseEvent e) { + clientPresent = false; + clientX = e.getX(); + clientY = e.getY(); + _mouseExited(e); + e.consume(); + } + + public final void mouseMoved(final MouseEvent e) { + clientX = e.getX(); + clientY = e.getY(); + _mouseMoved(e); + e.consume(); + } + + public final void mousePressed(final MouseEvent e) { + clientPressed = true; + clientX = e.getX(); + clientY = e.getY(); + _mousePressed(e); + e.consume(); + } + + public final void mouseReleased(final MouseEvent e) { + clientX = e.getX(); + clientY = e.getY(); + clientPressX = e.getX(); + clientPressY = e.getY(); + clientPressTime = System.currentTimeMillis(); + clientPressed = false; + + _mouseReleased(e); + e.consume(); + } + + public void mouseWheelMoved(final MouseWheelEvent e) { + try { + _mouseWheelMoved(e); + } catch (final AbstractMethodError ame) { + // it might not be implemented! + } + e.consume(); + } + + public final void sendEvent(final MouseEvent e) { + clientX = e.getX(); + clientY = e.getY(); + try { + if (e.getID() == MouseEvent.MOUSE_CLICKED) { + _mouseClicked(e); + } else if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + _mouseDragged(e); + } else if (e.getID() == MouseEvent.MOUSE_ENTERED) { + clientPresent = true; + _mouseEntered(e); + } else if (e.getID() == MouseEvent.MOUSE_EXITED) { + clientPresent = false; + _mouseExited(e); + } else if (e.getID() == MouseEvent.MOUSE_MOVED) { + _mouseMoved(e); + } else if (e.getID() == MouseEvent.MOUSE_PRESSED) { + clientPressed = true; + _mousePressed(e); + } else if (e.getID() == MouseEvent.MOUSE_RELEASED) { + clientPressX = e.getX(); + clientPressY = e.getY(); + clientPressTime = System.currentTimeMillis(); + clientPressed = false; + _mouseReleased(e); + } else if (e.getID() == MouseEvent.MOUSE_WHEEL) { + try { + _mouseWheelMoved((MouseWheelEvent) e); + } catch (final AbstractMethodError ignored) { + // it might not be implemented! + } + } else { + throw new InternalError(e.toString()); + } + } catch (final NullPointerException ignored) { + // client may throw NPE when a listener + // is being re-instantiated. + } + } +} diff --git a/src/org/rsbot/event/EventManager.java b/src/org/rsbot/event/EventManager.java new file mode 100644 index 0000000..0ddffcb --- /dev/null +++ b/src/org/rsbot/event/EventManager.java @@ -0,0 +1,209 @@ +package org.rsbot.event; + +import org.rsbot.event.events.RSEvent; + +import java.util.EventListener; +import java.util.EventObject; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class EventManager implements Runnable { + + public static class KillEvent extends RSEvent { + private static final long serialVersionUID = 3426050317048250049L; + + @Override + public void dispatch(final EventListener el) { + } + + @Override + public long getMask() { + return -1; + } + } + + private final Logger log = Logger.getLogger(EventManager.class.getName()); + + private final EventMulticaster multicaster = new EventMulticaster(); + private final Map queue = new HashMap(); + + private final Object threadLock = new Object(); + + private Thread eventThread; + + /** + * Adds the event to the queue for the EventManager to process. + *

+ * Events are processed with the default mask. + * + * @param e The event object to dispatch. + */ + public void dispatchEvent(final EventObject e) { + synchronized (queue) { + boolean added = false; + for (int off = 0; off < queue.size(); ++off) { + if (!queue.containsKey(off)) { + queue.put(off, e); + added = true; + break; + } + } + if (!added) { + queue.put(queue.size(), e); + } + queue.notifyAll(); + } + } + + /** + * Dispatches the given event. Calling this avoids the use + * of the event queue. + * + * @param event The event to fire. + */ + public void processEvent(final EventObject event) { + multicaster.fireEvent(event); + } + + /** + * Is this thread the event thread? + * + * @return true if the thread is an event thread; otherwise false. + */ + public boolean isEventThread() { + synchronized (threadLock) { + return Thread.currentThread() == eventThread; + } + } + + /** + * Is the event thread alive? + * + * @return true if the thread is alive; otherwise false. + */ + public boolean isEventThreadAlive() { + synchronized (threadLock) { + return eventThread != null; + } + } + + /** + * Kills the event manager thread. + * + * @param wait true to wait for the kill + * event to be processed before returning; otherwise + * false to submit the kill event and return + * immediately. + */ + public void killThread(final boolean wait) { + final EventObject event = new KillEvent(); + synchronized (event) { + dispatchEvent(event); + if (wait) { + try { + event.wait(); + } catch (final InterruptedException e) { + log.info("wait for kill event interrupted!"); + } + } + } + } + + /** + * Registers a listener. + * + * @param listener the listener to add. + */ + public void addListener(final EventListener listener) { + multicaster.addListener(listener); + } + + /** + * Registers a listener. + * + * @param listener the listener to add. + * @param mask the event type mask. + */ + public void addListener(final EventListener listener, final long mask) { + multicaster.addListener(listener, mask); + } + + /** + * Removes a listener. + * + * @param listener the listener to remove. + */ + public void removeListener(final EventListener listener) { + multicaster.removeListener(listener); + } + + /** + * The thread entry point. + */ + public void run() { + if (!isEventThread()) { + throw new IllegalThreadStateException(); + } + while (true) { + try { + EventObject event = null; + synchronized (queue) { + while (queue.isEmpty()) { + try { + queue.wait(); + } catch (final Exception e) { + log.info("Event Queue: " + e.toString()); + } + } + int emptySpots = 0; + for (int off = 0; off < queue.size() + emptySpots; ++off) { + if (!queue.containsKey(off)) { + emptySpots++; + continue; + } + event = queue.remove(off); + break; + } + } + if (event instanceof KillEvent) { + eventThread = null; + synchronized (event) { + event.notifyAll(); + } + return; + } + try { + processEvent(event); + } catch (final ThreadDeath td) { + eventThread = null; + event.notifyAll(); + return; + } catch (final Throwable e) { + e.printStackTrace(); + } + synchronized (event) { + event.notifyAll(); + } + } catch (final Exception e) { + log.info("Event Queue: " + e.toString()); + e.printStackTrace(); + } + } + } + + /** + * Spawns a daemon event thread. Only one can be created unless it is + * killed. + */ + public void start() { + synchronized (threadLock) { + if (eventThread != null) { + throw new IllegalThreadStateException(); + } + eventThread = new Thread(this, "EventQueue-" + hashCode()); + eventThread.setDaemon(true); + eventThread.start(); + } + } +} diff --git a/src/org/rsbot/event/EventMulticaster.java b/src/org/rsbot/event/EventMulticaster.java new file mode 100644 index 0000000..b25b40f --- /dev/null +++ b/src/org/rsbot/event/EventMulticaster.java @@ -0,0 +1,326 @@ +package org.rsbot.event; + +import org.rsbot.event.events.RSEvent; +import org.rsbot.event.listeners.CharacterMovedListener; +import org.rsbot.event.listeners.MessageListener; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.event.listeners.TextPaintListener; + +import java.awt.event.*; +import java.util.*; + +public class EventMulticaster implements EventListener { + + public static final long FOCUS_EVENT = 0x10; + public static final long KEY_EVENT = 0x08; + + public static final long MOUSE_EVENT = 0x01; + public static final long MOUSE_MOTION_EVENT = 0x02; + public static final long MOUSE_WHEEL_EVENT = 0x04; + + public static final long CHARACTER_MOVED_EVENT = 0x400; + public static final long SERVER_MESSAGE_EVENT = 0x600; + public static final long MESSAGE_EVENT = 0x800; + public static final long PAINT_EVENT = 0x1000; + public static final long TEXT_PAINT_EVENT = 0x2000; + + private static final Object treeLock = new Object(); + + /** + * Gets the default mask for an event listener. + */ + public static long getDefaultMask(final EventListener el) { + if (el instanceof EventMulticaster) { + final EventMulticaster em = (EventMulticaster) el; + return em.enabledMask; + } + int mask = 0; + if (el instanceof MouseListener) { + mask |= EventMulticaster.MOUSE_EVENT; + } + if (el instanceof MouseMotionListener) { + mask |= EventMulticaster.MOUSE_MOTION_EVENT; + } + if (el instanceof MouseWheelListener) { + mask |= EventMulticaster.MOUSE_WHEEL_EVENT; + } + if (el instanceof KeyListener) { + mask |= EventMulticaster.KEY_EVENT; + } + if (el instanceof FocusListener) { + mask |= EventMulticaster.FOCUS_EVENT; + } + if (el instanceof CharacterMovedListener) { + mask |= EventMulticaster.CHARACTER_MOVED_EVENT; + } + if (el instanceof MessageListener) { + mask |= EventMulticaster.MESSAGE_EVENT; + } + if (el instanceof PaintListener) { + mask |= EventMulticaster.PAINT_EVENT; + } + if (el instanceof TextPaintListener) { + mask |= EventMulticaster.TEXT_PAINT_EVENT; + } + + return mask; + } + + /** + * Gets the default mask for an event. + */ + public static long getDefaultMask(final EventObject e) { + long mask = 0; + if (e instanceof MouseEvent) { + final MouseEvent me = (MouseEvent) e; + switch (me.getID()) { + case MouseEvent.MOUSE_PRESSED: + case MouseEvent.MOUSE_RELEASED: + case MouseEvent.MOUSE_CLICKED: + case MouseEvent.MOUSE_ENTERED: + case MouseEvent.MOUSE_EXITED: + mask |= EventMulticaster.MOUSE_EVENT; + break; + + case MouseEvent.MOUSE_MOVED: + case MouseEvent.MOUSE_DRAGGED: + mask |= EventMulticaster.MOUSE_MOTION_EVENT; + break; + + case MouseEvent.MOUSE_WHEEL: + mask |= EventMulticaster.MOUSE_WHEEL_EVENT; + break; + } + } else if (e instanceof FocusEvent) { + final FocusEvent fe = (FocusEvent) e; + switch (fe.getID()) { + case FocusEvent.FOCUS_GAINED: + case FocusEvent.FOCUS_LOST: + mask |= EventMulticaster.FOCUS_EVENT; + break; + } + } else if (e instanceof KeyEvent) { + final KeyEvent ke = (KeyEvent) e; + switch (ke.getID()) { + case KeyEvent.KEY_TYPED: + case KeyEvent.KEY_PRESSED: + case KeyEvent.KEY_RELEASED: + mask |= EventMulticaster.KEY_EVENT; + break; + } + } else if (e instanceof RSEvent) { + final RSEvent rse = (RSEvent) e; + mask |= rse.getMask(); + } + return mask; + } + + private long enabledMask; + private final List listenerMasks = new ArrayList(); + + private final List listeners = new ArrayList(5); + + private EventMulticaster parent; + + /** + * Adds the listener to the tree with a default mask. + */ + public void addListener(final EventListener el) { + long mask; + if (el instanceof EventMulticaster) { + final EventMulticaster em = (EventMulticaster) el; + mask = em.enabledMask; + } else { + mask = EventMulticaster.getDefaultMask(el); + } + addListener(el, mask); + } + + /** + * Adds the listener with the specified mask. If its an EventMulticaster the + * specified mask will be ignored. + */ + public void addListener(final EventListener el, long mask) { + synchronized (EventMulticaster.treeLock) { + if (listeners.contains(el)) { + return; + } + + if (el instanceof EventMulticaster) { + final EventMulticaster em = (EventMulticaster) el; + addMulticaster(em); + mask = em.enabledMask; + } else { + listeners.add(el); + } + listenerMasks.add(mask); + // log.info(("Added mask: " + mask + " " + + // listenerMasks.get(listenerMasks.size()-1)); + cleanMasks(); + } + } + + /** + * Ensures the multicaster tree is clean and adds it. + *

+ * Has to hold tree lock. + */ + private void addMulticaster(final EventMulticaster em) { + if (em.parent != null) { + throw new IllegalArgumentException("adding multicaster to multiple multicasters"); + } + for (EventMulticaster cur = this; cur != null; cur = cur.parent) { + if (cur == em) { + throw new IllegalArgumentException("adding multicaster's parent to itself"); + } + } + listeners.add(em); + em.parent = this; + } + + /** + * Walks up the tree as necessary reseting the masks to the minimum. + *

+ * Has to hold TreeLock. + */ + private void cleanMasks() { + for (EventMulticaster cur = this; cur != null; cur = cur.parent) { + int mask = 0; + final int len = cur.listeners.size(); + for (int i = 0; i < len; i++) { + final EventListener el = cur.listeners.get(i); + long m = cur.listenerMasks.get(i); + if (el instanceof EventMulticaster) { + final EventMulticaster em = (EventMulticaster) el; + if (em.enabledMask != m) { + m = em.enabledMask; + cur.listenerMasks.set(i, m); + } + } + mask |= m; + } + if (mask == cur.enabledMask) { + break; + } + cur.enabledMask = mask; + } + } + + /** + * Fires an event to all applicable listeners. + */ + public void fireEvent(final EventObject e) { + fireEvent(e, EventMulticaster.getDefaultMask(e)); + } + + /** + * Fires an event to all listeners, restricted by the mask. + */ + public void fireEvent(final EventObject e, final long mask) { + synchronized (EventMulticaster.treeLock) { + final int len = listeners.size(); + for (int i = 0; i < len; i++) { + final long m = listenerMasks.get(i); + if (m != 12288 && (m & mask) == 0) { + continue; + } + final EventListener el = listeners.get(i); + if (e instanceof MouseEvent) { + final MouseEvent me = (MouseEvent) e; + switch (me.getID()) { + case MouseEvent.MOUSE_PRESSED: + ((MouseListener) el).mousePressed(me); + break; + case MouseEvent.MOUSE_RELEASED: + ((MouseListener) el).mouseReleased(me); + break; + case MouseEvent.MOUSE_CLICKED: + ((MouseListener) el).mouseClicked(me); + break; + case MouseEvent.MOUSE_ENTERED: + ((MouseListener) el).mouseEntered(me); + break; + case MouseEvent.MOUSE_EXITED: + ((MouseListener) el).mouseExited(me); + break; + case MouseEvent.MOUSE_MOVED: + ((MouseMotionListener) el).mouseMoved(me); + break; + case MouseEvent.MOUSE_DRAGGED: + ((MouseMotionListener) el).mouseDragged(me); + break; + case MouseEvent.MOUSE_WHEEL: + ((MouseWheelListener) el).mouseWheelMoved((MouseWheelEvent) me); + break; + } + } else if (e instanceof FocusEvent) { + final FocusEvent fe = (FocusEvent) e; + switch (fe.getID()) { + case FocusEvent.FOCUS_GAINED: + ((FocusListener) el).focusGained(fe); + break; + case FocusEvent.FOCUS_LOST: + ((FocusListener) el).focusLost(fe); + break; + } + } else if (e instanceof KeyEvent) { + final KeyEvent ke = (KeyEvent) e; + switch (ke.getID()) { + case KeyEvent.KEY_TYPED: + ((KeyListener) el).keyTyped(ke); + break; + case KeyEvent.KEY_PRESSED: + ((KeyListener) el).keyPressed(ke); + break; + case KeyEvent.KEY_RELEASED: + ((KeyListener) el).keyReleased(ke); + break; + } + } else if (e instanceof RSEvent) { + final RSEvent rse = (RSEvent) e; + rse.dispatch(el); + } + } + } + } + + /** + * Gets the masks enabled for this multicaster. + */ + public long getEnabledMask() { + return enabledMask; + } + + /** + * Returns an unmodifiable list of the backing list of listeners. + */ + public List getListeners() { + return Collections.unmodifiableList(listeners); + } + + /** + * Returns whether the mask is enabled on this multicaster. + */ + public final boolean isEnabled(final long mask) { + return (enabledMask & mask) != 0; + } + + /** + * Removes a listener. Cleans up the masks. + */ + public void removeListener(EventListener el) { + synchronized (EventMulticaster.treeLock) { + final int idx = listeners.indexOf(el); + if (idx == -1) { + return; + } + el = listeners.remove(idx); + if (el instanceof EventMulticaster) { + final EventMulticaster em = (EventMulticaster) el; + em.parent = null; + } + listenerMasks.remove(idx); + cleanMasks(); + } + } +} diff --git a/src/org/rsbot/event/events/CharacterMovedEvent.java b/src/org/rsbot/event/events/CharacterMovedEvent.java new file mode 100644 index 0000000..3880569 --- /dev/null +++ b/src/org/rsbot/event/events/CharacterMovedEvent.java @@ -0,0 +1,65 @@ +package org.rsbot.event.events; + +import org.rsbot.event.EventMulticaster; +import org.rsbot.event.listeners.CharacterMovedListener; +import org.rsbot.script.methods.MethodContext; + +import java.util.EventListener; + +/** + * A character moved event. + */ +public class CharacterMovedEvent extends RSEvent { + + private static final long serialVersionUID = 8883312847545757405L; + + private final MethodContext ctx; + private final org.rsbot.client.RSCharacter character; + private final int direction; + private org.rsbot.script.wrappers.RSCharacter wrapped; + + public CharacterMovedEvent(final MethodContext ctx, final org.rsbot.client.RSCharacter character, final int direction) { + this.ctx = ctx; + this.character = character; + this.direction = direction; + } + + @Override + public void dispatch(final EventListener el) { + ((CharacterMovedListener) el).characterMoved(this); + } + + public org.rsbot.script.wrappers.RSCharacter getCharacter() { + if (wrapped == null) { + if (character instanceof org.rsbot.client.RSNPC) { + final org.rsbot.client.RSNPC npc = (org.rsbot.client.RSNPC) character; + wrapped = new org.rsbot.script.wrappers.RSNPC(ctx, npc); + } else if (character instanceof org.rsbot.client.RSPlayer) { + final org.rsbot.client.RSPlayer player = (org.rsbot.client.RSPlayer) character; + wrapped = new org.rsbot.script.wrappers.RSPlayer(ctx, player); + } + } + return wrapped; + } + + /** + * 0 = NW + * 1 = N + * 2 = NE + * 3 = W + * 4 = E + * 5 = SW + * 6 = S + * 7 = SE + * + * @return Returns the direction of the character movement event. + */ + public int getDirection() { + return direction; + } + + @Override + public long getMask() { + return EventMulticaster.CHARACTER_MOVED_EVENT; + } +} diff --git a/src/org/rsbot/event/events/MessageEvent.java b/src/org/rsbot/event/events/MessageEvent.java new file mode 100644 index 0000000..251afb3 --- /dev/null +++ b/src/org/rsbot/event/events/MessageEvent.java @@ -0,0 +1,64 @@ +package org.rsbot.event.events; + +import org.rsbot.event.EventMulticaster; +import org.rsbot.event.listeners.MessageListener; + +import java.util.EventListener; + +/** + * A message event. + * + * @author Jacmob + */ +public class MessageEvent extends RSEvent { + + public static final int MESSAGE_SERVER = 0; + public static final int MESSAGE_CHAT = 2; + public static final int MESSAGE_PRIVATE_IN = 3; + public static final int MESSAGE_PRIVATE_INFO = 5; + public static final int MESSAGE_PRIVATE_OUT = 6; + public static final int MESSAGE_CLAN_CHAT = 9; + public static final int MESSAGE_CLIENT = 11; + public static final int MESSAGE_EXAMINE_NPC = 28; + public static final int MESSAGE_EXAMINE_OBJECT = 29; + public static final int MESSAGE_TRADE_REQ = 100; + public static final int MESSAGE_ASSIST_REQ = 102; + public static final int MESSAGE_TRADE_INFO = 103; + public static final int MESSAGE_ASSIST_INFO = 104; + public static final int MESSAGE_ACTION = 109; + + private static final long serialVersionUID = -8416382326776831211L; + + private final String sender; + private final int id; + private final String message; + + public MessageEvent(final String sender, final int id, final String message) { + this.sender = sender; + this.id = id; + this.message = message; + } + + @Override + public void dispatch(final EventListener el) { + ((MessageListener) el).messageReceived(this); + } + + @Override + public long getMask() { + return EventMulticaster.MESSAGE_EVENT; + } + + public String getSender() { + return sender; + } + + public int getID() { + return id; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/org/rsbot/event/events/PaintEvent.java b/src/org/rsbot/event/events/PaintEvent.java new file mode 100644 index 0000000..4ab8d28 --- /dev/null +++ b/src/org/rsbot/event/events/PaintEvent.java @@ -0,0 +1,54 @@ +package org.rsbot.event.events; + +import org.rsbot.event.EventMulticaster; +import org.rsbot.event.listeners.PaintListener; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.util.EventListener; + +/** + * A paint update event. + */ +public class PaintEvent extends RSEvent { + + private static final long serialVersionUID = -7404828108740551228L; + + public Graphics graphics; + + @Override + public void dispatch(final EventListener el) { + final Graphics2D g2d = (Graphics2D) graphics; + + // Store settings + final Color s_background = g2d.getBackground(); + final Shape s_clip = g2d.getClip(); + final Color s_color = g2d.getColor(); + final Composite s_composite = g2d.getComposite(); + final Font s_font = g2d.getFont(); + final Paint s_paint = g2d.getPaint(); + final RenderingHints s_renderingHints = g2d.getRenderingHints(); + final Stroke s_stroke = g2d.getStroke(); + final AffineTransform s_transform = g2d.getTransform(); + + // Dispatch the event + ((PaintListener) el).onRepaint(graphics); + + // Restore settings + g2d.setBackground(s_background); + g2d.setClip(s_clip); + g2d.setColor(s_color); + g2d.setComposite(s_composite); + g2d.setFont(s_font); + g2d.setPaint(s_paint); + g2d.setRenderingHints(s_renderingHints); + g2d.setStroke(s_stroke); + g2d.setTransform(s_transform); + } + + @Override + public long getMask() { + return EventMulticaster.PAINT_EVENT; + } + +} diff --git a/src/org/rsbot/event/events/RSEvent.java b/src/org/rsbot/event/events/RSEvent.java new file mode 100644 index 0000000..ee7848b --- /dev/null +++ b/src/org/rsbot/event/events/RSEvent.java @@ -0,0 +1,20 @@ +package org.rsbot.event.events; + +import java.util.EventListener; +import java.util.EventObject; + +public abstract class RSEvent extends EventObject { + + private static final long serialVersionUID = 6977096569226837605L; + + private static final Object SOURCE = new Object(); + + public RSEvent() { + super(RSEvent.SOURCE); + } + + public abstract void dispatch(EventListener el); + + public abstract long getMask(); + +} diff --git a/src/org/rsbot/event/events/TextPaintEvent.java b/src/org/rsbot/event/events/TextPaintEvent.java new file mode 100644 index 0000000..aa6c578 --- /dev/null +++ b/src/org/rsbot/event/events/TextPaintEvent.java @@ -0,0 +1,60 @@ +package org.rsbot.event.events; + +import org.rsbot.event.EventMulticaster; +import org.rsbot.event.listeners.TextPaintListener; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.util.EventListener; + +/** + * An event that specifies a line index and graphics + * object on which a TextPaintListener should paint a + * line of text. + */ +public class TextPaintEvent extends RSEvent { + + private static final long serialVersionUID = 6634362568916377937L; + + public Graphics graphics; + public int idx; + + @Override + public void dispatch(final EventListener el) { + final Graphics2D g2d = (Graphics2D) graphics; + + // Backup settings + // Which is needed, otherwise if some script does a transform without + // cleaning + // every other graphics call will use the same transformations. + final Color s_background = g2d.getBackground(); + final Shape s_clip = g2d.getClip(); + final Color s_color = g2d.getColor(); + final Composite s_composite = g2d.getComposite(); + final Font s_font = g2d.getFont(); + final Paint s_paint = g2d.getPaint(); + final RenderingHints s_renderingHints = g2d.getRenderingHints(); + final Stroke s_stroke = g2d.getStroke(); + final AffineTransform s_transform = g2d.getTransform(); + + // Dispatch the event + idx = ((TextPaintListener) el).drawLine(graphics, idx); + + // Restore settings + g2d.setBackground(s_background); + g2d.setClip(s_clip); + g2d.setColor(s_color); + g2d.setComposite(s_composite); + g2d.setFont(s_font); + g2d.setPaint(s_paint); + g2d.setRenderingHints(s_renderingHints); + g2d.setStroke(s_stroke); + g2d.setTransform(s_transform); + } + + @Override + public long getMask() { + return EventMulticaster.TEXT_PAINT_EVENT; + } + +} diff --git a/src/org/rsbot/event/impl/CharacterMovedLogger.java b/src/org/rsbot/event/impl/CharacterMovedLogger.java new file mode 100644 index 0000000..bd34d0c --- /dev/null +++ b/src/org/rsbot/event/impl/CharacterMovedLogger.java @@ -0,0 +1,16 @@ +package org.rsbot.event.impl; + +import org.rsbot.event.events.CharacterMovedEvent; +import org.rsbot.event.listeners.CharacterMovedListener; + +import java.util.logging.Logger; + +public class CharacterMovedLogger implements CharacterMovedListener { + + private final Logger log = Logger.getLogger(CharacterMovedLogger.class.getName()); + + @Override + public void characterMoved(final CharacterMovedEvent e) { + log.info("Character Moved: " + String.format("%2d %s", e.getDirection(), e.getCharacter().toString())); + } +} diff --git a/src/org/rsbot/event/impl/DrawBoundaries.java b/src/org/rsbot/event/impl/DrawBoundaries.java new file mode 100644 index 0000000..50341a0 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawBoundaries.java @@ -0,0 +1,113 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSTile; + +import java.awt.*; + +public class DrawBoundaries implements PaintListener { + + private final Point[][] minimapPoints = new Point[105][105]; + private final Point[][] screenPoints = new Point[105][105]; + + private final MethodContext ctx; + + public DrawBoundaries(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final int blocks[][] = ctx.client.getRSGroundDataArray()[ctx.client.getPlane()].getBlocks(); + final int baseX = ctx.client.getBaseX(); + final int baseY = ctx.client.getBaseY(); + for (int i = 0; i < screenPoints.length; i++) { + for (int j = 0; j < screenPoints[i].length; j++) { + final int x = i + baseX - 1; + final int y = j + baseY - 1; + Point mini = ctx.calc.worldToMinimap(x - 0.5, y - 0.5); + if (mini.x == -1 || mini.y == -1) { + mini = null; + } + minimapPoints[i][j] = mini; + Point screen = ctx.calc.tileToScreen(new RSTile(x, y), 0, 0, 0); + if (screen.x == -1 || screen.y == -1) { + screen = null; + } + screenPoints[i][j] = screen; + } + } + + render.setColor(Color.YELLOW); + for (int i = 1; i < 104; i++) { + for (int j = 1; j < 104; j++) { + final int curBlock = blocks[i][j]; + final Point miniBL = minimapPoints[i][j]; + final Point miniBR = minimapPoints[i][j + 1]; + final Point miniTL = minimapPoints[i + 1][j]; + final Point miniTR = minimapPoints[i + 1][j + 1]; + final Point bl = screenPoints[i][j]; + final Point br = screenPoints[i][j + 1]; + final Point tl = screenPoints[i + 1][j]; + final Point tr = screenPoints[i + 1][j + 1]; + if ((curBlock & 0x1280100) != 0) { + render.setColor(Color.black); + if (tl != null && br != null && tr != null && bl != null) { + render.fillPolygon(new int[]{bl.x, br.x, tr.x, tl.x}, new int[]{bl.y, br.y, tr.y, tl.y}, 4); + } + if (miniBL != null && miniBR != null && miniTR != null && miniTL != null) { + render.fillPolygon(new int[]{miniBL.x, miniBR.x, miniTR.x, miniTL.x}, + new int[]{miniBL.y, miniBR.y, miniTR.y, miniTL.y}, 4); + } + } + if ((blocks[i][j - 1] & 0x1280102) != 0 || (curBlock & 0x1280120) != 0) { + render.setColor(Color.RED); + if (tl != null && bl != null) { + render.drawLine(bl.x, bl.y, tl.x, tl.y); + } + if (miniBL != null && miniTL != null) { + render.drawLine(miniBL.x, miniBL.y, miniTL.x, miniTL.y); + } + } + if ((blocks[i - 1][j] & 0x1280108) != 0 || (curBlock & 0x1280180) != 0) { + render.setColor(Color.RED); + if (br != null && bl != null) { + render.drawLine(bl.x, bl.y, br.x, br.y); + } + if (miniBR != null && miniBL != null) { + render.drawLine(miniBL.x, miniBL.y, miniBR.x, miniBR.y); + } + } + /* + * render.setColor(Color.cyan); if ((curBlock & (1<<20)) != 0) { + * if (miniBL != null && miniBR != null && miniTR != null && + * miniTL != null) { render.fillPolygon(new + * int[]{miniBL.x,miniBR.x,miniTR.x,miniTL.x}, new + * int[]{miniBL.y,miniBR.y,miniTR.y,miniTL.y},4); } if (tl != + * null && br != null && tr != null && bl != null) { + * render.fillPolygon(new int[]{bl.x,br.x,tr.x,tl.x}, new + * int[]{bl.y,br.y,tr.y,tl.y},4); } } + */ + // Point miniCent = Calculations.worldToMinimap(i+ baseX, j+ + // baseY); + // Point cent = Calculations.tileToScreen(i+ baseX, j+ baseY, + // 0.5,0.5, 0); + /* + * if (cent.x != -1 && cent.y != -1) { + * render.setColor(Color.yellow); render.drawString("" + + * Calculations.getRealDistanceTo(cur.getX()-baseX, + * cur.getY()-baseY, i, j, false), (int)cent.getX(), + * (int)cent.getY()); } + */ + } + } + final Point mini = ctx.players.getMyPlayer().getMinimapLocation(); + render.setColor(Color.red); + render.fillRect((int) mini.getX() - 1, (int) mini.getY() - 1, 2, 2); + } +} diff --git a/src/org/rsbot/event/impl/DrawGround.java b/src/org/rsbot/event/impl/DrawGround.java new file mode 100644 index 0000000..7ad406a --- /dev/null +++ b/src/org/rsbot/event/impl/DrawGround.java @@ -0,0 +1,43 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSGroundItem; +import org.rsbot.script.wrappers.RSPlayer; +import org.rsbot.script.wrappers.RSTile; + +import java.awt.*; + +public class DrawGround implements PaintListener { + + private final MethodContext ctx; + + public DrawGround(final Bot bot) { + ctx = bot.getMethodContext(); + } + + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final RSPlayer player = ctx.players.getMyPlayer(); + if (player == null) { + return; + } + render.setColor(Color.WHITE); + final RSTile location = player.getLocation(); + for (int x = location.getX() - 25; x < location.getX() + 25; x++) { + for (int y = location.getY() - 25; y < location.getY() + 25; y++) { + final RSGroundItem[] item = ctx.groundItems.getAllAt(x, y); + if (item == null || item.length == 0) { + continue; + } + final Point screen = ctx.calc.tileToScreen(item[0].getLocation()); + if (ctx.calc.pointOnScreen(screen)) { + render.drawString("" + item[0].getItem().getID(), location.getX() - 10, location.getY()); + } + } + } + } +} diff --git a/src/org/rsbot/event/impl/DrawInventory.java b/src/org/rsbot/event/impl/DrawInventory.java new file mode 100644 index 0000000..4a459e8 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawInventory.java @@ -0,0 +1,37 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.Game; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSItem; + +import java.awt.*; + +public class DrawInventory implements PaintListener { + private final MethodContext ctx; + + public DrawInventory(final Bot bot) { + ctx = bot.getMethodContext(); + } + + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + + if (ctx.game.getTab() != Game.Tab.INVENTORY) { + return; + } + + render.setColor(Color.WHITE); + final RSItem[] inventoryItems = ctx.inventory.getItems(); + + for (final RSItem inventoryItem : inventoryItems) { + if (inventoryItem.getID() != -1) { + final Point location = inventoryItem.getComponent().getCenter(); + render.drawString("" + inventoryItem.getID(), location.x, location.y); + } + } + } +} diff --git a/src/org/rsbot/event/impl/DrawItems.java b/src/org/rsbot/event/impl/DrawItems.java new file mode 100644 index 0000000..4d17aa5 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawItems.java @@ -0,0 +1,63 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSGroundItem; +import org.rsbot.script.wrappers.RSModel; +import org.rsbot.script.wrappers.RSPlayer; +import org.rsbot.script.wrappers.RSTile; + +import java.awt.*; + +public class DrawItems implements PaintListener { + + private final MethodContext ctx; + + public DrawItems(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final RSPlayer player = ctx.players.getMyPlayer(); + if (player == null) { + return; + } + final FontMetrics metrics = render.getFontMetrics(); + final RSTile location = player.getLocation(); + final int locX = location.getX(); + final int locY = location.getY(); + final int tHeight = metrics.getHeight(); + for (int x = locX - 25; x < locX + 25; x++) { + for (int y = locY - 25; y < locY + 25; y++) { + final Point screen = ctx.calc.tileToScreen(new RSTile(x, y)); + if (!ctx.calc.pointOnScreen(screen)) { + continue; + } + final RSGroundItem[] items = ctx.groundItems.getAllAt(x, y); + if (items.length > 0) { + final RSModel model = items[0].getModel(); + if (model != null) { + render.setColor(Color.BLUE); + for (final Polygon polygon : model.getTriangles()) { + render.drawPolygon(polygon); + } + } + } + for (int i = 0; i < items.length; i++) { + render.setColor(Color.RED); + render.fillRect((int) screen.getX() - 1, (int) screen.getY() - 1, 2, 2); + final String s = "" + items[i].getItem().getID(); + final int ty = screen.y - tHeight * (i + 1) + tHeight / 2; + final int tx = screen.x - metrics.stringWidth(s) / 2; + render.setColor(Color.green); + render.drawString(s, tx, ty); + } + } + } + } +} diff --git a/src/org/rsbot/event/impl/DrawModel.java b/src/org/rsbot/event/impl/DrawModel.java new file mode 100644 index 0000000..2d88696 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawModel.java @@ -0,0 +1,142 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSGroundItem; +import org.rsbot.script.wrappers.RSModel; +import org.rsbot.script.wrappers.RSObject; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.HashMap; + +/** + * @author Jacmob + * @author Kosaki + */ +public class DrawModel implements PaintListener, MouseListener { + + private static final HashMap color_map = new HashMap(); + + static { + color_map.put(RSObject.Type.BOUNDARY, Color.BLACK); + color_map.put(RSObject.Type.FLOOR_DECORATION, Color.YELLOW); + color_map.put(RSObject.Type.INTERACTABLE, Color.WHITE); + color_map.put(RSObject.Type.WALL_DECORATION, Color.GRAY); + } + + private static final String[] OPTIONS = {"Objects", "Players", "NPCs", "Piles"}; + private static boolean[] enabled = {true, true, true, true}; + + private final MethodContext ctx; + + public DrawModel(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public void onRepaint(final Graphics render) { + drawRect(render); + if (enabled[0]) { + for (final org.rsbot.script.wrappers.RSObject o : ctx.objects.getAll()) { + final RSModel model = o.getModel(); + if (model != null) { + render.setColor(color_map.get(o.getType())); + for (final Polygon polygon : model.getTriangles()) { + render.drawPolygon(polygon); + } + render.setColor(Color.GREEN); + final Point p = model.getPoint(); + render.fillOval(p.x - 1, p.y - 1, 2, 2); + } + } + } + if (enabled[1]) { + for (final org.rsbot.script.wrappers.RSCharacter c : ctx.players.getAll()) { + final RSModel model = c.getModel(); + if (model != null) { + render.setColor(Color.RED); + for (final Polygon polygon : model.getTriangles()) { + render.drawPolygon(polygon); + } + } + } + } + if (enabled[2]) { + for (final org.rsbot.script.wrappers.RSCharacter c : ctx.npcs.getAll()) { + final RSModel model = c.getModel(); + if (model != null) { + render.setColor(Color.MAGENTA); + for (final Polygon polygon : model.getTriangles()) { + render.drawPolygon(polygon); + } + } + } + } + if (enabled[3]) { + for (final RSGroundItem item : ctx.groundItems.getAll()) { + final RSModel model = item.getModel(); + if (model != null) { + render.setColor(Color.CYAN); + for (final Polygon polygon : model.getTriangles()) { + render.drawPolygon(polygon); + } + } + } + } + } + + public final void drawRect(final Graphics render) { + final Color j = Color.BLACK; + final Color w = Color.WHITE; + for (int i = 0; i < OPTIONS.length; i++) { + final int alpha = 150; + render.setColor(new Color(j.getRed(), j.getGreen(), j.getBlue(), alpha)); + if (enabled[i]) { + render.setColor(new Color(w.getRed(), w.getGreen(), w.getBlue(), alpha)); + } + render.fillRect(90 + 80 * i, 3, 80, 12); + render.setColor(Color.white); + if (enabled[i]) { + render.setColor(Color.BLACK); + } + render.drawString(OPTIONS[i], 90 + 80 * i + 10, 13); + render.setColor(Color.black); + render.drawRect(90 + 80 * i, 3, 80, 12); + } + } + + @Override + public void mouseClicked(final MouseEvent e) { + for (int i = 0; i < OPTIONS.length; i++) { + final Rectangle rect = new Rectangle(90 + 80 * i, 3, 80, 12); + if (rect.contains(e.getPoint())) { + enabled[i] = !enabled[i]; + e.consume(); + return; + } + } + } + + @Override + public void mouseEntered(final MouseEvent arg0) { + + } + + @Override + public void mouseExited(final MouseEvent arg0) { + + } + + @Override + public void mousePressed(final MouseEvent arg0) { + + } + + @Override + public void mouseReleased(final MouseEvent arg0) { + + } +} diff --git a/src/org/rsbot/event/impl/DrawMouse.java b/src/org/rsbot/event/impl/DrawMouse.java new file mode 100644 index 0000000..40bbd4c --- /dev/null +++ b/src/org/rsbot/event/impl/DrawMouse.java @@ -0,0 +1,110 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.client.Client; +import org.rsbot.client.input.Mouse; +import org.rsbot.event.listeners.PaintListener; + +import java.awt.*; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class DrawMouse implements PaintListener { + private final Client client; + private final List clicks = new LinkedList(); + private final Object lock = new Object(); + + public DrawMouse(final Bot bot) { + client = bot.getClient(); + } + + private double getRot() { + return System.currentTimeMillis() % 3600 / 10.0D; + } + + @Override + public void onRepaint(final Graphics render) { + final Mouse mouse = client.getMouse(); + if (mouse != null) { + final Point location = new Point(mouse.getX(), mouse.getY()); + final Graphics2D g = (Graphics2D) render.create(); + final Graphics2D gg = (Graphics2D) render.create(); + g.setColor(Color.GREEN); + g.rotate(Math.toRadians(getRot()), location.x, location.y); + g.drawLine(location.x, location.y - 5, location.x, location.y + 5); + g.drawLine(location.x - 5, location.y, location.x + 5, location.y); + if (mouse.isPressed() && (clicks.size() > 0 && clicks.get(clicks.size() - 1).getAge() > 100 && clicks.get(clicks.size() - 1).getStart() != mouse.getPressTime() || clicks.size() == 0)) { + final Cross newCross = new Cross(1500, mouse.getPressTime(), location, getRot()); + if (!clicks.contains(newCross)) { + clicks.add(newCross); + } + } + synchronized (lock) { + final Iterator clickIterator = clicks.listIterator(); + while (clickIterator.hasNext()) { + final Cross toDraw = clickIterator.next(); + if (toDraw.handle()) { + drawPoint(toDraw.getLocation(), toDraw.getRot(), gg, toDraw.getAlpha()); + } else { + clicks.remove(toDraw); + } + } + } + } + } + + private void drawPoint(final Point location, final double rot, final Graphics2D g, final int al) { + final Graphics2D g1 = (Graphics2D) g.create(); + g1.setColor(new Color(255, 0, 0, al)); + g1.rotate(rot, location.x, location.y); + g1.drawLine(location.x, location.y - 5, location.x, location.y + 5); + g1.drawLine(location.x - 5, location.y, location.x + 5, location.y); + } + + private class Cross { + private final long time, st; + private final Point location; + private final double rot; + + public Cross(final long lifetime, final long st, final Point loc, final double rot) { + time = System.currentTimeMillis() + lifetime; + location = loc; + this.rot = rot; + this.st = st; + } + + public long getStart() { + return st; + } + + public long getAge() { + return time - System.currentTimeMillis(); + } + + public int getAlpha() { + return Math.min(255, Math.max(0, (int) (256.0D * getAge() / 1500.0D))); + } + + public boolean handle() { + return System.currentTimeMillis() <= time; + } + + public double getRot() { + return rot; + } + + public Point getLocation() { + return location; + } + + @Override + public boolean equals(final Object o) { + if (o instanceof Cross) { + final Cross oo = (Cross) o; + return oo.location.equals(location); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/event/impl/DrawNPCs.java b/src/org/rsbot/event/impl/DrawNPCs.java new file mode 100644 index 0000000..5760b19 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawNPCs.java @@ -0,0 +1,53 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.client.Node; +import org.rsbot.client.RSNPCNode; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSNPC; + +import java.awt.*; + +public class DrawNPCs implements PaintListener { + + private final MethodContext ctx; + + public DrawNPCs(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + + final FontMetrics metrics = render.getFontMetrics(); + for (final int element : ctx.client.getRSNPCIndexArray()) { + final Node node = ctx.nodes.lookup(ctx.client.getRSNPCNC(), element); + if (node == null || !(node instanceof RSNPCNode)) { + continue; + } + final RSNPC npc = new RSNPC(ctx, ((RSNPCNode) node).getRSNPC()); + final Point location = ctx.calc.tileToScreen(npc.getLocation(), npc.getHeight() / 2); + if (!ctx.calc.pointOnScreen(location)) { + continue; + } + render.setColor(Color.RED); + render.fillRect((int) location.getX() - 1, (int) location.getY() - 1, 2, 2); + String s = npc.getID() + (npc.getLevel() > 0 ? " (" + npc.getLevel() + ")" : ""); + render.setColor(npc.isInCombat() ? (npc.isDead() ? Color.gray : Color.red) : npc.isMoving() ? Color.green : Color.WHITE); + render.drawString(s, location.x - metrics.stringWidth(s) / 2, location.y - metrics.getHeight() / 2); + // int x = element.getX(); + // x -= ((int)(x >> 7)) << 7; + if (npc.getAnimation() != -1 || npc.getGraphic() > 0) { + s = "(A: " + npc.getAnimation() + " | G: " + npc.getGraphic() + ")"; + render.drawString(s, location.x - metrics.stringWidth(s) / 2, location.y - metrics.getHeight() * 3 / 2); + } + // s = "" + element.isMoving(); + // render.drawString(s, location.x - metrics.stringWidth(s) / 2, + // location.y - metrics.getHeight() * 5 / 2); + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/event/impl/DrawObjects.java b/src/org/rsbot/event/impl/DrawObjects.java new file mode 100644 index 0000000..2ddf740 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawObjects.java @@ -0,0 +1,73 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSObject; +import org.rsbot.script.wrappers.RSPlayer; +import org.rsbot.script.wrappers.RSTile; + +import java.awt.*; +import java.util.HashMap; + +public class DrawObjects implements PaintListener { + + private final MethodContext ctx; + + public DrawObjects(final Bot bot) { + ctx = bot.getMethodContext(); + } + + private static final HashMap color_map = new HashMap(); + + static { + color_map.put(RSObject.Type.BOUNDARY, Color.BLACK); + color_map.put(RSObject.Type.FLOOR_DECORATION, Color.YELLOW); + color_map.put(RSObject.Type.INTERACTABLE, Color.WHITE); + color_map.put(RSObject.Type.WALL_DECORATION, Color.GRAY); + } + + @Override + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final RSPlayer player = ctx.players.getMyPlayer(); + if (player == null) { + return; + } + final FontMetrics metrics = render.getFontMetrics(); + final RSTile location = player.getLocation(); + final int locX = location.getX(); + final int locY = location.getY(); + final int tHeight = metrics.getHeight(); + for (int x = locX - 25; x < locX + 25; x++) { + for (int y = locY - 25; y < locY + 25; y++) { + final RSTile tile = new RSTile(x, y); + final Point screen = ctx.calc.tileToScreen(tile); + if (!ctx.calc.pointOnScreen(screen)) { + continue; + } + final RSObject[] objects = ctx.objects.getAllAt(tile); + int i = 0; + for (final RSObject object : objects) { + final Point real = ctx.calc.tileToScreen(object.getLocation()); + if (!ctx.calc.pointOnScreen(real)) { + continue; + } + if (screen.x > -1) { + render.setColor(Color.GREEN); + render.fillRect(screen.x - 1, screen.y - 1, 2, 2); + render.setColor(Color.RED); + render.drawLine(screen.x, screen.y, real.x, real.y); + } + final String s = "" + object.getID(); + final int ty = real.y - tHeight / 2 - i++ * 15; + final int tx = real.x - metrics.stringWidth(s) / 2; + render.setColor(color_map.get(object.getType())); + render.drawString(s, tx, ty); + } + } + } + } +} diff --git a/src/org/rsbot/event/impl/DrawPlayers.java b/src/org/rsbot/event/impl/DrawPlayers.java new file mode 100644 index 0000000..0b4cb30 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawPlayers.java @@ -0,0 +1,60 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.wrappers.RSPlayer; + +import java.awt.*; + +public class DrawPlayers implements PaintListener { + + private final MethodContext ctx; + + public DrawPlayers(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final org.rsbot.client.RSPlayer[] players = ctx.client.getRSPlayerArray(); + if (players == null) { + return; + } + final FontMetrics metrics = render.getFontMetrics(); + for (final org.rsbot.client.RSPlayer element : players) { + if (element == null) { + continue; + } + final RSPlayer player = new RSPlayer(ctx, element); + final Point location = ctx.calc.tileToScreen(player.getLocation(), player.getHeight() / 2); + if (!ctx.calc.pointOnScreen(location)) { + continue; + } + render.setColor(Color.RED); + render.fillRect((int) location.getX() - 1, (int) location.getY() - 1, 2, 2); + String s = player.getName() + " (" + player.getCombatLevel() + ")"; + render.setColor(player.isInCombat() ? (player.isDead() ? Color.GRAY : Color.RED) : player.isMoving() ? Color.GREEN : Color.WHITE); + render.drawString(s, location.x - metrics.stringWidth(s) / 2, location.y - metrics.getHeight() / 2); + final String msg = player.getMessage(); + boolean raised = false; + if (player.getAnimation() != -1 || player.getGraphic() > 0 || player.getNPCID() != -1) { + if (player.getNPCID() != -1) { + s = "(NPC: " + player.getNPCID() + " | L: " + player.getLevel() + " | A: " + player.getAnimation() + " | G: " + player.getGraphic() + ")"; + } else { + s = "(A: " + player.getAnimation() + " | G: " + player.getGraphic() + ")"; + } + render.drawString(s, location.x - metrics.stringWidth(s) / 2, location.y - metrics.getHeight() * 3 / 2); + raised = true; + } + if (msg != null) { + render.setColor(Color.ORANGE); + render.drawString(msg, location.x - metrics.stringWidth(msg) / 2, + location.y - metrics.getHeight() * (raised ? 5 : 3) / 2); + } + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/event/impl/DrawSettings.java b/src/org/rsbot/event/impl/DrawSettings.java new file mode 100644 index 0000000..ab8bf03 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawSettings.java @@ -0,0 +1,61 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.Settings; + +import java.awt.*; +import java.util.Arrays; + +public class DrawSettings implements PaintListener { + + private static final Font monoFont = Font.decode(Font.MONOSPACED); + private static final int TIMEOUT = 5000; + + private int[] lastSettings = new int[0]; + private long[] settingsAge = new long[0]; + + private final Settings settings; + + public DrawSettings(final Bot bot) { + settings = bot.getMethodContext().settings; + } + + @Override + public void onRepaint(final Graphics render) { + final int[] settings = this.settings.getSettingArray(); + if (settings != null) { + final Font prev = render.getFont(); + render.setFont(DrawSettings.monoFont); + if (settings.length > lastSettings.length) { + lastSettings = Arrays.copyOf(lastSettings, settings.length); + settingsAge = Arrays.copyOf(settingsAge, settings.length); + } + int id = 0; + final long curTime = System.currentTimeMillis(); + final long cutoffTime = curTime - DrawSettings.TIMEOUT; + for (int i = 0; i < settings.length; i++) { + if (settingsAge[i] == 0) { + settingsAge[i] = settings[i] != 0 ? cutoffTime : curTime; + } + if (lastSettings[i] != settings[i]) { + settingsAge[i] = curTime; + lastSettings[i] = settings[i]; + } + final boolean highlight = settingsAge[i] > cutoffTime; + final boolean show = settings[i] != 0; + if (show || highlight) { + final int x = 10 + 140 * (id % 4); + final int y = 70 + 12 * (id / 4); + final String s = String.format("%4d: %d", i, settings[i]); + render.setColor(Color.black); + render.drawString(s, x, y + 1); + render.setColor(highlight ? Color.red : Color.green); + render.drawString(s, x, y); + id++; + } + } + render.setFont(prev); + } + } +} diff --git a/src/org/rsbot/event/impl/DrawWeb.java b/src/org/rsbot/event/impl/DrawWeb.java new file mode 100644 index 0000000..c602ef6 --- /dev/null +++ b/src/org/rsbot/event/impl/DrawWeb.java @@ -0,0 +1,64 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.internal.wrappers.TileData; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.methods.Web; +import org.rsbot.script.wrappers.RSPlayer; +import org.rsbot.script.wrappers.RSTile; + +import java.awt.*; +import java.util.Iterator; +import java.util.Map; + +/** + * Draws the web. + * + * @author Timer + */ +public class DrawWeb implements PaintListener { + private final MethodContext ctx; + + /** + * Calculates a point to the minimap. + * + * @param tile The tile to calculate. + * @param player Your player. + * @return The point of the tile. + */ + private Point tileToMap(final RSTile tile, final RSTile player) { + final double minimapAngle = -1 * Math.toRadians(ctx.camera.getAngle()); + final int x = (tile.getX() - player.getX()) * 4 - 2; + final int y = (player.getY() - tile.getY()) * 4 - 2; + return new Point((int) Math.round(x * Math.cos(minimapAngle) + y * Math.sin(minimapAngle) + 628), (int) Math.round(y * Math.cos(minimapAngle) - x * Math.sin(minimapAngle) + 87)); + } + + public DrawWeb(final Bot bot) { + ctx = bot.getMethodContext(); + } + + public void onRepaint(final Graphics render) { + if (!ctx.game.isLoggedIn()) { + return; + } + final RSPlayer player = ctx.players.getMyPlayer(); + if (player == null) { + return; + } + final RSTile oT = player.getLocation(); + final int plane = ctx.game.getPlane(); + final Iterator> rs = Web.rs_map.entrySet().iterator(); + while (rs.hasNext()) { + final Map.Entry e = rs.next(); + final Short[] tile = e.getKey(); + final RSTile t = new RSTile(tile[0], tile[1], tile[2]); + final int key = e.getValue(); + if (t.getZ() == plane && ctx.calc.distanceBetween(t, oT) < 105) { + render.setColor(TileData.Questionable(key) ? Color.yellow : TileData.Special(key) ? Color.cyan : Color.red); + final Point p = tileToMap(t, oT); + render.drawLine(p.x, p.y, p.x, p.y); + } + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/event/impl/MessageLogger.java b/src/org/rsbot/event/impl/MessageLogger.java new file mode 100644 index 0000000..9f5d440 --- /dev/null +++ b/src/org/rsbot/event/impl/MessageLogger.java @@ -0,0 +1,20 @@ +package org.rsbot.event.impl; + +import org.rsbot.event.events.MessageEvent; +import org.rsbot.event.listeners.MessageListener; + +import java.util.logging.Logger; + +public class MessageLogger implements MessageListener { + + private final Logger log = Logger.getLogger(MessageLogger.class.getName()); + + @Override + public void messageReceived(final MessageEvent e) { + if (e.getSender().equals("")) { + log.info("[" + e.getID() + "] " + e.getMessage()); + } else { + log.info("[" + e.getID() + "] " + e.getSender() + ": " + e.getMessage()); + } + } +} diff --git a/src/org/rsbot/event/impl/TAnimation.java b/src/org/rsbot/event/impl/TAnimation.java new file mode 100644 index 0000000..2df6ae4 --- /dev/null +++ b/src/org/rsbot/event/impl/TAnimation.java @@ -0,0 +1,30 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TAnimation implements TextPaintListener { + + private final MethodContext ctx; + + public TAnimation(final Bot bot) { + ctx = bot.getMethodContext(); + } + + @Override + public int drawLine(final Graphics render, int idx) { + int animation; + if (ctx.game.isLoggedIn()) { + animation = ctx.players.getMyPlayer().getAnimation(); + } else { + animation = -1; + } + StringUtil.drawLine(render, idx++, "Animation " + animation); + return idx; + } + +} diff --git a/src/org/rsbot/event/impl/TCamera.java b/src/org/rsbot/event/impl/TCamera.java new file mode 100644 index 0000000..5875310 --- /dev/null +++ b/src/org/rsbot/event/impl/TCamera.java @@ -0,0 +1,29 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.client.Client; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TCamera implements TextPaintListener { + + private final Client client; + + public TCamera(final Bot bot) { + client = bot.getClient(); + } + + @Override + public int drawLine(final Graphics render, int idx) { + final String camPos = "Camera Position (x,y,z): (" + client.getCamPosX() + ", " + client.getCamPosY() + ", " + client.getCamPosZ() + ")"; + final String camAngle = "Camera Angle (pitch, yaw): (" + client.getCameraPitch() + ", " + client.getCameraYaw() + ")"; + //final String detailLvl = "Detail lvl: " + (client.getDetailInfo() != null ? client.getDetailInfo().getDetailLevel() : "null"); + + StringUtil.drawLine(render, idx++, camPos); + StringUtil.drawLine(render, idx++, camAngle); + //Methods.drawLine(render, idx++, detailLvl); + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TFPS.java b/src/org/rsbot/event/impl/TFPS.java new file mode 100644 index 0000000..2c9b5d5 --- /dev/null +++ b/src/org/rsbot/event/impl/TFPS.java @@ -0,0 +1,32 @@ +package org.rsbot.event.impl; + +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TFPS implements TextPaintListener { + + private static final int LEN = 2; + + private final int[] frameCount = new int[TFPS.LEN]; + + private int lastIdx = 0; + + @Override + public int drawLine(final Graphics render, int idx) { + final int secTime = (int) (System.currentTimeMillis() / 1000); + + final int prevIdx = (secTime - 1) % TFPS.LEN; + StringUtil.drawLine(render, idx++, String.format("%2d fps", frameCount[prevIdx])); + + final int curIdx = secTime % TFPS.LEN; + if (lastIdx != curIdx) { + lastIdx = curIdx; + frameCount[curIdx] = 1; + } else { + frameCount[curIdx]++; + } + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TFloorHeight.java b/src/org/rsbot/event/impl/TFloorHeight.java new file mode 100644 index 0000000..8a69416 --- /dev/null +++ b/src/org/rsbot/event/impl/TFloorHeight.java @@ -0,0 +1,25 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.Game; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TFloorHeight implements TextPaintListener { + + private final Game game; + + public TFloorHeight(final Bot bot) { + game = bot.getMethodContext().game; + } + + @Override + public int drawLine(final Graphics render, int idx) { + final int floor = game.getPlane(); + StringUtil.drawLine(render, idx++, "Floor " + floor); + return idx; + } + +} diff --git a/src/org/rsbot/event/impl/TLoginIndex.java b/src/org/rsbot/event/impl/TLoginIndex.java new file mode 100644 index 0000000..e8be76c --- /dev/null +++ b/src/org/rsbot/event/impl/TLoginIndex.java @@ -0,0 +1,24 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.Game; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TLoginIndex implements TextPaintListener { + + private final Game game; + + public TLoginIndex(final Bot bot) { + game = bot.getMethodContext().game; + } + + @Override + public int drawLine(final Graphics render, int idx) { + StringUtil.drawLine(render, idx++, "Client State: " + game.getClientState()); + return idx; + } + +} diff --git a/src/org/rsbot/event/impl/TMenu.java b/src/org/rsbot/event/impl/TMenu.java new file mode 100644 index 0000000..d5ec7fe --- /dev/null +++ b/src/org/rsbot/event/impl/TMenu.java @@ -0,0 +1,29 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.client.Client; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TMenu implements TextPaintListener { + + private final Client client; + + public TMenu(final Bot bot) { + client = bot.getClient(); + } + + @Override + public int drawLine(final Graphics render, int idx) { + StringUtil.drawLine(render, idx++, "Menu " + (client.isMenuOpen() ? "Open" : "Closed") + + " & " + (client.isMenuCollapsed() ? "Collapsed" : "Expanded")); + StringUtil.drawLine(render, idx++, "Menu Location: (" + + client.getMenuX() + "," + client.getMenuY() + ")"); + StringUtil.drawLine(render, idx++, "Sub-Menu Location: (" + + client.getSubMenuX() + "," + client.getSubMenuY() + ")"); + StringUtil.drawLine(render, idx++, "Sub-Menu Width: " + client.getSubMenuWidth()); + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TMenuActions.java b/src/org/rsbot/event/impl/TMenuActions.java new file mode 100644 index 0000000..77f82fa --- /dev/null +++ b/src/org/rsbot/event/impl/TMenuActions.java @@ -0,0 +1,27 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.Menu; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TMenuActions implements TextPaintListener { + + private final Menu menu; + + public TMenuActions(final Bot bot) { + menu = bot.getMethodContext().menu; + } + + @Override + public int drawLine(final Graphics render, int idx) { + final String[] items = menu.getItems(); + int i = 0; + for (final String item : items) { + StringUtil.drawLine(render, idx++, i++ + ": [red]" + item); + } + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TMousePosition.java b/src/org/rsbot/event/impl/TMousePosition.java new file mode 100644 index 0000000..a4cd065 --- /dev/null +++ b/src/org/rsbot/event/impl/TMousePosition.java @@ -0,0 +1,31 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.client.Client; +import org.rsbot.client.input.Mouse; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TMousePosition implements TextPaintListener { + + private final Client client; + + public TMousePosition(final Bot bot) { + client = bot.getClient(); + } + + @Override + public int drawLine(final Graphics render, int idx) { + final Mouse mouse = client.getMouse(); + if (mouse != null) { + final int mouse_x = mouse.getX(); + final int mouse_y = mouse.getY(); + final String off = mouse.isPresent() ? "" : " (off)"; + StringUtil.drawLine(render, idx++, "Mouse Position: (" + mouse_x + "," + mouse_y + ")" + off); + } + + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TPlayerPosition.java b/src/org/rsbot/event/impl/TPlayerPosition.java new file mode 100644 index 0000000..1932916 --- /dev/null +++ b/src/org/rsbot/event/impl/TPlayerPosition.java @@ -0,0 +1,26 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.Players; +import org.rsbot.script.wrappers.RSTile; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TPlayerPosition implements TextPaintListener { + + private final Players players; + + public TPlayerPosition(final Bot bot) { + players = bot.getMethodContext().players; + } + + @Override + public int drawLine(final Graphics render, int idx) { + final RSTile position = players.getMyPlayer().getLocation(); + StringUtil.drawLine(render, idx++, "Position: " + position); + return idx; + } + +} diff --git a/src/org/rsbot/event/impl/TTab.java b/src/org/rsbot/event/impl/TTab.java new file mode 100644 index 0000000..0c5b9bd --- /dev/null +++ b/src/org/rsbot/event/impl/TTab.java @@ -0,0 +1,27 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.script.methods.Game; +import org.rsbot.script.methods.Game.Tab; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TTab implements TextPaintListener { + + private final Game game; + + public TTab(final Bot bot) { + game = bot.getMethodContext().game; + } + + @Override + public int drawLine(final Graphics render, int idx) { + final Tab cTab = game.getTab(); + StringUtil.drawLine(render, idx++, + "Current Tab: " + cTab.description() + " (" + cTab.index() + ")"); + return idx; + } + +} diff --git a/src/org/rsbot/event/impl/TUserInputAllowed.java b/src/org/rsbot/event/impl/TUserInputAllowed.java new file mode 100644 index 0000000..f1526ed --- /dev/null +++ b/src/org/rsbot/event/impl/TUserInputAllowed.java @@ -0,0 +1,24 @@ +package org.rsbot.event.impl; + +import org.rsbot.bot.Bot; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +public class TUserInputAllowed implements TextPaintListener { + + private final Bot bot; + + public TUserInputAllowed(final Bot bot) { + this.bot = bot; + } + + @Override + public int drawLine(final Graphics render, int idx) { + final String i = bot.overrideInput || bot.inputFlags == 3 ? "[green]Enabled" : + "[red]Disabled (" + bot.inputFlags + ")"; + StringUtil.drawLine(render, idx++, "User Input: " + i); + return idx; + } +} diff --git a/src/org/rsbot/event/impl/TWebStatus.java b/src/org/rsbot/event/impl/TWebStatus.java new file mode 100644 index 0000000..7da0c54 --- /dev/null +++ b/src/org/rsbot/event/impl/TWebStatus.java @@ -0,0 +1,26 @@ +package org.rsbot.event.impl; + +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.service.WebQueue; +import org.rsbot.util.StringUtil; + +import java.awt.*; + +/** + * Draws the web cache and cache writer information. + * + * @author Timer + */ +public class TWebStatus implements TextPaintListener { + public TWebStatus() { + } + + public int drawLine(final Graphics render, int idx) { + final String[] items = {"Web Queue", "Buffering: " + WebQueue.weAreBuffering + ", " + WebQueue.bufferingCount + " nodes.", "Speed Buffering: " + WebQueue.speedBuffer, + "Cache Writer", "Queue Size: " + WebQueue.queueSize(0), "Remove queue size: " + WebQueue.queueSize(1), "Removing queue size: " + WebQueue.queueSize(2)}; + for (final String item : items) { + StringUtil.drawLine(render, idx++, item); + } + return idx; + } +} diff --git a/src/org/rsbot/event/listeners/CharacterMovedListener.java b/src/org/rsbot/event/listeners/CharacterMovedListener.java new file mode 100644 index 0000000..35708d5 --- /dev/null +++ b/src/org/rsbot/event/listeners/CharacterMovedListener.java @@ -0,0 +1,15 @@ +/** + * + */ +package org.rsbot.event.listeners; + +import org.rsbot.event.events.CharacterMovedEvent; + +import java.util.EventListener; + +/** + * @author Qauters + */ +public interface CharacterMovedListener extends EventListener { + public void characterMoved(CharacterMovedEvent e); +} diff --git a/src/org/rsbot/event/listeners/MessageListener.java b/src/org/rsbot/event/listeners/MessageListener.java new file mode 100644 index 0000000..b13aff5 --- /dev/null +++ b/src/org/rsbot/event/listeners/MessageListener.java @@ -0,0 +1,9 @@ +package org.rsbot.event.listeners; + +import org.rsbot.event.events.MessageEvent; + +import java.util.EventListener; + +public interface MessageListener extends EventListener { + abstract void messageReceived(MessageEvent e); +} diff --git a/src/org/rsbot/event/listeners/PaintListener.java b/src/org/rsbot/event/listeners/PaintListener.java new file mode 100644 index 0000000..b28d0e4 --- /dev/null +++ b/src/org/rsbot/event/listeners/PaintListener.java @@ -0,0 +1,8 @@ +package org.rsbot.event.listeners; + +import java.awt.*; +import java.util.EventListener; + +public interface PaintListener extends EventListener { + public void onRepaint(Graphics render); +} diff --git a/src/org/rsbot/event/listeners/TextPaintListener.java b/src/org/rsbot/event/listeners/TextPaintListener.java new file mode 100644 index 0000000..386ada4 --- /dev/null +++ b/src/org/rsbot/event/listeners/TextPaintListener.java @@ -0,0 +1,13 @@ +package org.rsbot.event.listeners; + +import java.awt.*; +import java.util.EventListener; + +public interface TextPaintListener extends EventListener { + /** + * Argument is the line number to start drawing from. + *

+ * Returns the line number + number of lines actually drawn. + */ + public int drawLine(Graphics render, int idx); +} diff --git a/src/org/rsbot/gui/AccountManager.java b/src/org/rsbot/gui/AccountManager.java new file mode 100644 index 0000000..7c3416b --- /dev/null +++ b/src/org/rsbot/gui/AccountManager.java @@ -0,0 +1,419 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.script.AccountStore; +import org.rsbot.service.DRM; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Logger; + +/** + * @author Tekk + * @author Jacmob + * @author Aion + * @author Timer + * @author Paris + */ +public class AccountManager extends JDialog implements ActionListener { + private static final long serialVersionUID = 2834954922670757338L; + + private static final String FILE_ACCOUNT_STORAGE = Configuration.Paths.getAccountsFile(); + + private static final String[] RANDOM_REWARDS = {"Cash", "Runes", "Coal", "Essence", "Ore", "Bars", "Gems", "Herbs", + "Seeds", "Charms", "Surprise", "Emote", "Costume", "Attack", + "Defence", "Strength", "Constitution", "Range", "Prayer", "Magic", + "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", + "Crafting", "Smithing", "Mining", "Herblore", "Agility", "Thieving", + "Slayer", "Farming", "Runecrafting", "Hunter", "Construction", + "Summoning", "Dungeoneering"}; + + private static final String[] VALID_KEYS = {"pin", "reward", "member", "take_breaks"}; + + private static final Logger log = Logger.getLogger(AccountManager.class.getName()); + + private static final AccountStore accountStore = new AccountStore(new File(FILE_ACCOUNT_STORAGE)); + + static { + accountStore.setPassword(DRM.getServiceKey()); + try { + accountStore.load(); + } catch (final IOException ignored) { + } + } + + private static class RandomRewardEditor extends DefaultCellEditor { + private static final long serialVersionUID = 6519185448833736787L; + + public RandomRewardEditor() { + super(new JComboBox(RANDOM_REWARDS)); + } + } + + private static class PasswordCellEditor extends DefaultCellEditor { + private static final long serialVersionUID = -8042183192369284908L; + + public PasswordCellEditor() { + super(new JPasswordField()); + } + } + + private static class PasswordCellRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = -8149913137634230574L; + + @Override + protected void setValue(final Object value) { + if (value == null) { + setText(""); + } else { + final String str = value.toString(); + final StringBuilder b = new StringBuilder(); + for (int i = 0; i < str.length(); ++i) { + b.append("\u25CF"); + } + setText(b.toString()); + } + } + } + + private class TableSelectionListener implements ListSelectionListener { + public void valueChanged(final ListSelectionEvent evt) { + final int row = table.getSelectedRow(); + if (!evt.getValueIsAdjusting()) { + removeButton.setEnabled(row >= 0 && row < table.getRowCount()); + } + } + } + + private class AccountTableModel extends AbstractTableModel { + private static final long serialVersionUID = 3233063410900758383L; + + public int getRowCount() { + return accountStore.list().size(); + } + + public int getColumnCount() { + return VALID_KEYS.length + 2; + } + + public Object getValueAt(final int row, final int column) { + if (column == 0) { + return userForRow(row); + } else if (column == 1) { + return accountStore.get(userForRow(row)).getPassword(); + } else { + final AccountStore.Account acc = accountStore.get(userForRow(row)); + if (acc != null) { + final String str = acc.getAttribute(VALID_KEYS[column - 2]); + if (str == null || str.isEmpty()) { + return null; + } + if (getColumnClass(column) == Boolean.class) { + return Boolean.parseBoolean(str); + } else if (getColumnClass(column) == Integer.class) { + return Integer.parseInt(str); + } else { + return str; + } + } + } + return null; + } + + @Override + public String getColumnName(final int column) { + if (column == 0) { + return "Username"; + } else if (column == 1) { + return "Password"; + } + final String str = VALID_KEYS[column - 2]; + final StringBuilder b = new StringBuilder(); + boolean space = true; + for (char c : str.toCharArray()) { + if (c == '_') { + c = ' '; + } + b.append(space ? Character.toUpperCase(c) : c); + space = c == ' '; + } + return b.toString(); + } + + @Override + public Class getColumnClass(final int column) { + if (getColumnName(column).equals("Member")) { + return Boolean.class; + } + if (getColumnName(column).equals("Take Breaks")) { + return Boolean.class; + } + return Object.class; + } + + @Override + public boolean isCellEditable(final int row, final int column) { + return column > 0; + } + + @Override + public void setValueAt(final Object value, final int row, final int column) { + final AccountStore.Account acc = accountStore.get(userForRow(row)); + if (acc == null) { + return; + } + if (column == 1) { + acc.setPassword(String.valueOf(value)); + } else { + acc.setAttribute(getColumnName(column).toLowerCase().replace(' ', '_'), String.valueOf(value)); + } + fireTableCellUpdated(row, column); + } + + public String userForRow(final int row) { + final Iterator it = accountStore.list().iterator(); + for (int k = 0; it.hasNext() && k < row; k++) { + it.next(); + } + if (it.hasNext()) { + return it.next().getUsername(); + } + return null; + } + } + + private JTable table; + private JButton removeButton; + + private AccountManager() { + super(Frame.getFrames()[0], "Account Manager", true); + setIconImage(Configuration.getImage(Configuration.Paths.Resources.ICON_REPORTKEY)); + } + + public void actionPerformed(final ActionEvent e) { + if (e.getSource() instanceof JButton) { + final JButton button = (JButton) e.getSource(); + if (button.getText().equals("Save")) { + try { + accountStore.save(); + } catch (final IOException ioe) { + ioe.printStackTrace(); + log.info("Failed to save accounts... Please report this."); + } + dispose(); + } else if (button.getToolTipText().equals("Add")) { + final String str = JOptionPane.showInputDialog(getParent(), "Enter the account username:", "New Account", JOptionPane.QUESTION_MESSAGE); + if (str == null || str.isEmpty()) { + return; + } + accountStore.add(new AccountStore.Account(str)); + accountStore.get(str).setAttribute("reward", RANDOM_REWARDS[0]); + final int row = table.getRowCount(); + ((AccountTableModel) table.getModel()).fireTableRowsInserted(row, row); + } else if (button.getToolTipText().equals("Remove")) { + final int row = table.getSelectedRow(); + final String user = ((AccountTableModel) table.getModel()).userForRow(row); + if (user != null) { + accountStore.remove(user); + ((AccountTableModel) table.getModel()).fireTableRowsDeleted(row, row); + } + } + } + } + + /** + * Creates and displays the main GUI This GUI has the list and the main * buttons + */ + public void showGUI() { + final JScrollPane scrollPane = new JScrollPane(); + table = new JTable(new AccountTableModel()); + final JToolBar bar = new JToolBar(); + bar.setMargin(new Insets(1, 1, 1, 1)); + bar.setFloatable(false); + removeButton = new JButton("Remove", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_CLOSE))); + final JButton newButton = new JButton("Add", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_ADD))); + final JButton doneButton = new JButton("Save", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_REPORT_DISK))); + setTitle("Account Manager"); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.getSelectionModel().addListSelectionListener(new TableSelectionListener()); + table.setShowGrid(false); + final TableColumnModel cm = table.getColumnModel(); + cm.getColumn(cm.getColumnIndex("Password")).setCellRenderer(new PasswordCellRenderer()); + cm.getColumn(cm.getColumnIndex("Password")).setCellEditor(new PasswordCellEditor()); + cm.getColumn(cm.getColumnIndex("Pin")).setCellRenderer(new PasswordCellRenderer()); + cm.getColumn(cm.getColumnIndex("Pin")).setCellEditor(new PasswordCellEditor()); + cm.getColumn(cm.getColumnIndex("Reward")).setCellEditor(new RandomRewardEditor()); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setViewportView(table); + add(scrollPane, BorderLayout.CENTER); + newButton.setFocusable(false); + newButton.setToolTipText(newButton.getText()); + newButton.setText(""); + bar.add(newButton); + removeButton.setFocusable(false); + removeButton.setToolTipText(removeButton.getText()); + removeButton.setText(""); + bar.add(removeButton); + bar.add(Box.createHorizontalGlue()); + bar.add(doneButton); + newButton.addActionListener(this); + removeButton.addActionListener(this); + doneButton.addActionListener(this); + add(bar, BorderLayout.SOUTH); + final int row = table.getSelectedRow(); + removeButton.setEnabled(row >= 0 && row < table.getRowCount()); + table.clearSelection(); + doneButton.requestFocus(); + setPreferredSize(new Dimension(600, 300)); + pack(); + setLocationRelativeTo(getOwner()); + setResizable(false); + setVisible(true); + } + + /** + * Access the list of names for loaded accounts + * + * @return Array of the names. + */ + public static String[] getAccountNames() { + try { + final List theList = new ArrayList(); + final Collection accountCollection = AccountManager.accountStore.list(); + for (final AccountStore.Account anAccountCollection : accountCollection) { + final AccountStore.Account account = anAccountCollection; + theList.add(account.getUsername()); + } + return theList.toArray(new String[theList.size()]); + } catch (final Exception e) { + e.printStackTrace(); + } + return null; + } + + public static AccountManager getInstance() { + return new AccountManager(); + } + + /** + * Access the account password of the given string + * + * @param name The name of the account + * @return Password or an empty string + */ + public static String getPassword(final String name) { + final AccountStore.Account values = AccountManager.accountStore.get(name); + String pass = values.getPassword(); + if (pass == null) { + pass = ""; + } + return pass; + } + + /** + * Access the account pin of the given string + * + * @param name The name of the account + * @return Pin or an empty string + */ + public static String getPin(final String name) { + final AccountStore.Account values = AccountManager.accountStore.get(name); + String pin = values.getAttribute("pin"); + if (pin == null) { + pin = "-1"; + } + return pin; + } + + /** + * Access the account desired reward of the given string + * + * @param name The name of the account + * @return The desired reward + */ + public static String getReward(final String name) { + final AccountStore.Account values = AccountManager.accountStore.get(name); + final String reward = values.getAttribute("reward"); + if (reward == null) { + return "Cash"; + } + return reward; + } + + /** + * Access the account state of the given string + * + * @param name Name of the account + * @return true if the account is member, false if it isn't + */ + public static boolean isMember(final String name) { + final AccountStore.Account values = AccountManager.accountStore.get(name); + final String member = values.getAttribute("member"); + return member != null && member.equalsIgnoreCase("true"); + } + + /** + * Access the account state of the given string + * + * @param name Name of the account + * @return true if the account is member, false if it isn't + */ + public static boolean isTakingBreaks(final String name) { + final AccountStore.Account values = AccountManager.accountStore.get(name); + final String member = values.getAttribute("take_breaks"); + return member != null && member.equalsIgnoreCase("true"); + } + + /** + * Check if the string is a valid key + * + * @param key The key + * @return true if the object is supported, false if it isn't + */ + @SuppressWarnings("unused") + private static boolean isValidKey(final String key) { + for (final String check : VALID_KEYS) { + if (key.equalsIgnoreCase(check)) { + return true; + } + } + return false; + } + + /** + * Checks if the given string is a valid pin + * + * @param pin The pin + * @return true if the pin is valid, false if it isn't + */ + @SuppressWarnings("unused") + private static boolean isValidPin(final String pin) { + if (pin.length() == 4) { + for (int i = 0; i < pin.length(); i++) { + final char charAt = pin.charAt(i); + if (charAt < '0' || charAt > '9') { + return false; + } + } + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/org/rsbot/gui/BotGUI.java b/src/org/rsbot/gui/BotGUI.java new file mode 100644 index 0000000..a909fe8 --- /dev/null +++ b/src/org/rsbot/gui/BotGUI.java @@ -0,0 +1,663 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.Configuration.OperatingSystem; +import org.rsbot.bot.Bot; +import org.rsbot.gui.component.Messages; +import org.rsbot.log.TextAreaLogHandler; +import org.rsbot.script.Script; +import org.rsbot.script.ScriptManifest; +import org.rsbot.script.internal.ScriptHandler; +import org.rsbot.script.internal.event.ScriptListener; +import org.rsbot.script.methods.Environment; +import org.rsbot.script.provider.ScriptDownloader; +import org.rsbot.script.util.WindowUtil; +import org.rsbot.service.Monitoring; +import org.rsbot.service.Monitoring.Type; +import org.rsbot.service.TwitterUpdates; +import org.rsbot.service.WebQueue; +import org.rsbot.util.ApplicationException; +import org.rsbot.util.UpdateChecker; +import org.rsbot.util.io.IOHelper; +import org.rsbot.util.io.ScreenshotUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.TrayIcon.MessageType; +import java.awt.event.*; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.List; +import java.util.logging.Logger; + +/** + * @author Paris + * @author Jacmob + */ +public class BotGUI extends JFrame implements ActionListener, ScriptListener { + public static final int PANEL_WIDTH = 765, PANEL_HEIGHT = 503, LOG_HEIGHT = 120; + public static final int MAX_BOTS = 6; + private static final long serialVersionUID = -5411033752001988794L; + private static final Logger log = Logger.getLogger(BotGUI.class.getName()); + private SettingsManager settings; + private SettingsManager.Preferences prefs; + private BotPanel panel; + private JScrollPane scrollableBotPanel; + private BotToolBar toolBar; + private BotMenuBar menuBar; + private JScrollPane textScroll; + private BotHome home; + private final List bots = new ArrayList(); + private TrayIcon tray = null; + private java.util.Timer shutdown = null; + private java.util.Timer clean = null; + + public BotGUI() throws ApplicationException { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (final Exception ignored) { + } + if (UpdateChecker.isError()) { + throw new ApplicationException("Unable to obtain latest version information.\nPlease check your internet connection and try again."); + } else if (Configuration.RUNNING_FROM_JAR && UpdateChecker.isDeprecatedVersion()) { + throw new ApplicationException("This version has been deprecated, please update at " + Configuration.Paths.URLs.DOWNLOAD); + } + init(); + pack(); + setTitle(null); + setLocationRelativeTo(getOwner()); + setMinimumSize(new Dimension((int) (getSize().width * .8), (int) (getSize().height * .8))); + setResizable(true); + settings = new SettingsManager(this, new File(Configuration.Paths.getSettingsDirectory(), "preferences.ini")); + prefs = settings.getPreferences(); + prefs.load(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); + if (!prefs.ads) { + new SplashAd(BotGUI.this).display(); + } + UpdateChecker.notify(BotGUI.this); + if (Configuration.Twitter.ENABLED) { + TwitterUpdates.loadTweets(Configuration.Twitter.MESSAGES); + } + Monitoring.start(); + addBot(); + updateScriptControls(); + setShutdownTimer(prefs.shutdown); + System.gc(); + } + }); + clean = new java.util.Timer(true); + clean.schedule(new TimerTask() { + @Override + public void run() { + System.gc(); + } + }, 1000 * 60 * 10, 1000 * 60 * 10); + } + + @Override + public void setTitle(final String title) { + String t = Configuration.NAME + " v" + Configuration.getVersionFormatted(); + final int v = Configuration.getVersion(), l = UpdateChecker.getLatestVersion(); + if (v > l) { + t += " beta"; + } + if (title != null) { + t = title + " - " + t; + } + super.setTitle(t); + } + + public void actionPerformed(final ActionEvent evt) { + final String action = evt.getActionCommand(); + final String menu, option; + final int z = action.indexOf('.'); + if (z == -1) { + menu = action; + option = ""; + } else { + menu = action.substring(0, z); + option = action.substring(z + 1); + } + if (menu.equals(Messages.CLOSEBOT)) { + if (confirmRemoveBot()) { + final int idx = Integer.parseInt(option); + removeBot(bots.get(idx)); + } + } else if (menu.equals(Messages.FILE)) { + if (option.equals(Messages.NEWBOT)) { + addBot(); + } else if (option.equals(Messages.CLOSEBOT)) { + if (confirmRemoveBot()) { + removeBot(getCurrentBot()); + } + } else if (option.equals(Messages.ADDSCRIPT)) { + final String pretext = ""; + final String key = (String) JOptionPane.showInputDialog(this, "Enter the script URL e.g. pastebin link or direct compiled file:", + option, JOptionPane.QUESTION_MESSAGE, null, null, pretext); + if (!(key == null || key.trim().isEmpty())) { + ScriptDownloader.save(key); + } + } else if (option.equals(Messages.RUNSCRIPT)) { + final Bot current = getCurrentBot(); + if (current != null) { + showScriptSelector(current); + } + } else if (option.equals(Messages.STOPSCRIPT)) { + final Bot current = getCurrentBot(); + if (current != null) { + showStopScript(current); + } + } else if (option.equals(Messages.PAUSESCRIPT)) { + final Bot current = getCurrentBot(); + if (current != null) { + pauseScript(current); + } + } else if (option.equals(Messages.SAVESCREENSHOT)) { + final Bot current = getCurrentBot(); + if (current != null) { + ScreenshotUtil.saveScreenshot(current, current.getMethodContext().game.isLoggedIn()); + } + } else if (option.equals(Messages.HIDEBOT)) { + setTray(); + } else if (option.equals(Messages.EXIT)) { + cleanExit(false); + } + } else if (menu.equals(Messages.EDIT)) { + if (option.equals(Messages.ACCOUNTS)) { + AccountManager.getInstance().showGUI(); + } else { + final Bot current = getCurrentBot(); + if (current != null) { + if (option.equals(Messages.FORCEINPUT)) { + final boolean selected = ((JCheckBoxMenuItem) evt.getSource()).isSelected(); + current.overrideInput = selected; + updateScriptControls(); + } else if (option.equals(Messages.LESSCPU)) { + lessCpu(((JCheckBoxMenuItem) evt.getSource()).isSelected()); + } else if (option.equals(Messages.EXTDVIEWS)) { + menuBar.setExtendedView(((JCheckBoxMenuItem) evt.getSource()).isSelected()); + } else if (option.equals(Messages.DISABLEANTIRANDOMS)) { + current.disableRandoms = ((JCheckBoxMenuItem) evt.getSource()).isSelected(); + } else if (option.equals(Messages.DISABLEAUTOLOGIN)) { + current.disableAutoLogin = ((JCheckBoxMenuItem) evt.getSource()).isSelected(); + } + } + } + } else if (menu.equals(Messages.VIEW)) { + final Bot current = getCurrentBot(); + final boolean selected = ((JCheckBoxMenuItem) evt.getSource()).isSelected(); + if (option.equals(Messages.HIDETOOLBAR)) { + toggleViewState(toolBar, selected); + } else if (option.equals(Messages.HIDELOGPANE)) { + toggleViewState(textScroll, selected); + } else if (current != null) { + if (option.equals(Messages.ALLDEBUGGING)) { + for (final String key : BotMenuBar.DEBUG_MAP.keySet()) { + final Class el = BotMenuBar.DEBUG_MAP.get(key); + if (menuBar.getCheckBox(key).isVisible()) { + final boolean wasSelected = menuBar.getCheckBox(key).isSelected(); + menuBar.getCheckBox(key).setSelected(selected); + if (selected) { + if (!wasSelected) { + current.addListener(el); + } + } else { + if (wasSelected) { + current.removeListener(el); + } + } + } + } + } else { + final Class el = BotMenuBar.DEBUG_MAP.get(option); + menuBar.getCheckBox(option).setSelected(selected); + if (selected) { + current.addListener(el); + } else { + menuBar.getCheckBox(Messages.ALLDEBUGGING).setSelected(false); + current.removeListener(el); + } + } + } + } else if (menu.equals(Messages.TOOLS)) { + if (option.equals(Messages.CLEARCACHE)) { + final int result = JOptionPane.showConfirmDialog(this, + "Delete all preferences and settings?\nNote: only use if the application is having errors.", option, + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + IOHelper.recursiveDelete(new File(Configuration.Paths.getCacheDirectory()), false); + IOHelper.recursiveDelete(new File(Configuration.Paths.getSettingsDirectory()), false); + log.info("Cache cleared and preferences reset to defaults"); + } + } else if (option.equals(Messages.OPTIONS)) { + settings.display(); + } + } else if (menu.equals(Messages.HELP)) { + if (option.equals(Messages.SITE)) { + openURL(Configuration.Paths.URLs.SITE); + } else if (option.equals(Messages.PROJECT)) { + openURL(Configuration.Paths.URLs.PROJECT); + } else if (option.equals(Messages.ABOUT)) { + JOptionPane.showMessageDialog(this, new String[]{"An open source bot developed by the community.", "Visit " + Configuration.Paths.URLs.SITE + "/ for more information."}, option, JOptionPane.INFORMATION_MESSAGE); + } + } else if (menu.equals("Tab")) { + final Bot curr = getCurrentBot(); + menuBar.setBot(curr); + panel.setBot(curr); + panel.repaint(); + toolBar.setHome(curr == null); + setTitle(curr == null ? null : curr.getAccountName()); + updateScriptControls(); + } + } + + public void updateScriptControls() { + updateScriptControls(false); + } + + public void updateScriptControls(final boolean block) { + boolean idle = true, paused = false; + final Bot bot = getCurrentBot(); + + if (bot != null) { + final Map scriptMap = bot.getScriptHandler().getRunningScripts(); + if (scriptMap.size() > 0) { + idle = false; + paused = scriptMap.values().iterator().next().isPaused(); + } else { + idle = true; + } + } + + if (block) { + idle = false; + } + + menuBar.getMenuItem(Messages.RUNSCRIPT).setVisible(idle); + menuBar.getMenuItem(Messages.STOPSCRIPT).setVisible(!idle); + menuBar.getMenuItem(Messages.PAUSESCRIPT).setEnabled(!idle); + menuBar.setPauseScript(paused); + toolBar.setInputButtonVisible(!idle); + menuBar.setEnabled(Messages.FORCEINPUT, !idle); + + if (idle) { + toolBar.setOverrideInput(false); + menuBar.setOverrideInput(false); + toolBar.setInputState(Environment.INPUT_KEYBOARD | Environment.INPUT_MOUSE); + toolBar.setScriptButton(BotToolBar.RUN_SCRIPT); + } else { + toolBar.setOverrideInput(bot.overrideInput); + toolBar.setOverrideInput(bot.overrideInput); + toolBar.setInputState(bot.inputFlags); + toolBar.setScriptButton(paused ? BotToolBar.RESUME_SCRIPT : BotToolBar.PAUSE_SCRIPT); + } + + toolBar.updateInputButton(); + } + + private void lessCpu(boolean on) { + disableRendering(on || menuBar.isTicked(Messages.LESSCPU)); + } + + public void disableRendering(final boolean mode) { + for (final Bot bot : bots) { + bot.disableRendering = mode; + } + } + + public BotPanel getPanel() { + return panel; + } + + public Bot getBot(final Object o) { + final ClassLoader cl = o.getClass().getClassLoader(); + for (final Bot bot : bots) { + if (cl == bot.getLoader().getClient().getClass().getClassLoader()) { + panel.offset(); + return bot; + } + } + return null; + } + + public void addBot() { + if (bots.size() > MAX_BOTS) { + return; + } + final Bot bot = new Bot(); + bots.add(bot); + toolBar.addTab(); + toolBar.setAddTabVisible(bots.size() < MAX_BOTS); + bot.getScriptHandler().addScriptListener(this); + new Thread(new Runnable() { + public void run() { + bot.start(); + home.setBots(bots); + } + }).start(); + } + + public void removeBot(final Bot bot) { + final int idx = bots.indexOf(bot); + bot.getScriptHandler().stopAllScripts(); + bot.getScriptHandler().removeScriptListener(this); + bot.getBackgroundScriptHandler().stopAllScripts(); + if (idx >= 0) { + toolBar.removeTab(idx); + } + bots.remove(idx); + home.setBots(bots); + toolBar.setAddTabVisible(bots.size() < MAX_BOTS); + new Thread(new Runnable() { + public void run() { + bot.stop(); + System.gc(); + } + }).start(); + } + + void pauseScript(final Bot bot) { + final ScriptHandler sh = bot.getScriptHandler(); + final Map running = sh.getRunningScripts(); + if (running.size() > 0) { + final int id = running.keySet().iterator().next(); + sh.pauseScript(id); + } + } + + private Bot getCurrentBot() { + final int idx = toolBar.getCurrentTab(); + if (idx >= 0) { + return bots.get(idx); + } + return null; + } + + private void showScriptSelector(final Bot bot) { + if (AccountManager.getAccountNames() == null || AccountManager.getAccountNames().length == 0) { + JOptionPane.showMessageDialog(this, "No accounts found! Please create one before using the bot."); + AccountManager.getInstance().showGUI(); + } else if (bot.getMethodContext() == null) { + log.warning("The client is still loading"); + } else { + new ScriptSelector(this, bot).showGUI(); + } + } + + private void showStopScript(final Bot bot) { + final ScriptHandler sh = bot.getScriptHandler(); + final Map running = sh.getRunningScripts(); + if (running.size() > 0) { + final int id = running.keySet().iterator().next(); + final Script s = running.get(id); + final ScriptManifest prop = s.getClass().getAnnotation(ScriptManifest.class); + final int result = JOptionPane.showConfirmDialog(this, "Would you like to stop the script " + prop.name() + "?", "Script", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.OK_OPTION) { + sh.stopScript(id); + updateScriptControls(); + } + } + } + + private void toggleViewState(final Component component, final boolean visible) { + final Dimension size = getSize(); + size.height += component.getSize().height * (visible ? -1 : 1); + component.setVisible(!visible); + setMinimumSize(size); + if ((getExtendedState() & Frame.MAXIMIZED_BOTH) != Frame.MAXIMIZED_BOTH) { + pack(); + } + } + + private void init() { + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent e) { + if (cleanExit(false)) { + dispose(); + } + } + }); + addWindowStateListener(new WindowStateListener() { + public void windowStateChanged(final WindowEvent arg0) { + switch (arg0.getID()) { + case WindowEvent.WINDOW_ICONIFIED: + lessCpu(true); + break; + case WindowEvent.WINDOW_DEICONIFIED: + lessCpu(false); + break; + } + } + }); + setIconImage(Configuration.getImage(Configuration.Paths.Resources.ICON)); + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + WindowUtil.setFrame(this); + home = new BotHome(); + panel = new BotPanel(home); + menuBar = new BotMenuBar(this); + toolBar = new BotToolBar(this, menuBar); + panel.setFocusTraversalKeys(0, new HashSet()); + menuBar.setBot(null); + setJMenuBar(menuBar); + textScroll = new JScrollPane(TextAreaLogHandler.TEXT_AREA, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + textScroll.setBorder(null); + textScroll.setPreferredSize(new Dimension(PANEL_WIDTH, LOG_HEIGHT)); + textScroll.setVisible(true); + scrollableBotPanel = new JScrollPane(panel); + add(toolBar, BorderLayout.NORTH); + add(scrollableBotPanel, BorderLayout.CENTER); + add(textScroll, BorderLayout.SOUTH); + } + + public void scriptStarted(final ScriptHandler handler, final Script script) { + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + final Bot bot = handler.getBot(); + if (bot == getCurrentBot()) { + bot.inputFlags = Environment.INPUT_KEYBOARD; + bot.overrideInput = false; + updateScriptControls(); + final String acct = bot.getAccountName(); + toolBar.setTabLabel(bots.indexOf(bot), acct == null ? Messages.TABDEFAULTTEXT : acct); + setTitle(acct); + } + } + }); + } + + public void scriptStopped(final ScriptHandler handler, final Script script) { + final Bot bot = handler.getBot(); + if (bot == getCurrentBot()) { + bot.inputFlags = Environment.INPUT_KEYBOARD | Environment.INPUT_MOUSE; + bot.overrideInput = false; + updateScriptControls(); + toolBar.setTabLabel(bots.indexOf(bot), Messages.TABDEFAULTTEXT); + setTitle(null); + } + } + + public void scriptResumed(final ScriptHandler handler, final Script script) { + if (handler.getBot() == getCurrentBot()) { + updateScriptControls(); + } + } + + public void scriptPaused(final ScriptHandler handler, final Script script) { + if (handler.getBot() == getCurrentBot()) { + updateScriptControls(); + } + } + + public void inputChanged(final Bot bot, final int mask) { + bot.inputFlags = mask; + toolBar.setInputState(mask); + updateScriptControls(); + } + + public static void openURL(final String url) { + final Configuration.OperatingSystem os = Configuration.getCurrentOperatingSystem(); + try { + if (os == Configuration.OperatingSystem.MAC) { + final Class fileMgr = Class.forName("com.apple.eio.FileManager"); + final Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[]{String.class}); + openURL.invoke(null, url); + } else if (os == Configuration.OperatingSystem.WINDOWS) { + Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); + } else { + final String[] browsers = {"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape", "google-chrome", "chromium-browser"}; + String browser = null; + for (int count = 0; count < browsers.length && browser == null; count++) { + if (Runtime.getRuntime().exec(new String[]{"which", browsers[count]}).waitFor() == 0) { + browser = browsers[count]; + } + } + if (browser == null) { + throw new Exception("Could not find web browser"); + } else { + Runtime.getRuntime().exec(new String[]{browser, url}); + } + } + } catch (final Exception e) { + log.warning("Unable to open " + url); + } + } + + private boolean confirmRemoveBot() { + if (!prefs.confirmations) { + final int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to close this bot?", Messages.CLOSEBOT, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + return result == JOptionPane.OK_OPTION; + } else { + return true; + } + } + + public void setShutdownTimer(final boolean enabled) { + if (!enabled) { + if (shutdown != null) { + shutdown.cancel(); + shutdown.purge(); + } + shutdown = null; + } else { + final long interval = prefs.shutdownTime * 60 * 1000; + shutdown = new java.util.Timer(true); + shutdown.schedule(new TimerTask() { + @Override + public void run() { + for (final Bot bot : bots) { + if (bot.getScriptHandler().getRunningScripts().size() != 0) { + return; + } + } + final int delay = 3; + log.info("Shutdown pending in " + delay + " minutes..."); + final Point[] mouse = new Point[]{MouseInfo.getPointerInfo().getLocation(), null}; + try { + Thread.sleep(delay * 60 * 1000); + } catch (InterruptedException ignored) { + } + mouse[1] = MouseInfo.getPointerInfo().getLocation(); + if (mouse[0].x != mouse[1].x || mouse[0].y != mouse[1].y) { + log.info("Mouse activity detected, delaying shutdown"); + } else if (!prefs.shutdown) { + log.info("Shutdown cancelled"); + } else if (Configuration.getCurrentOperatingSystem() == OperatingSystem.WINDOWS) { + try { + Runtime.getRuntime().exec("shutdown.exe", new String[]{"-s"}); + cleanExit(true); + } catch (IOException ignored) { + log.severe("Could not shutdown system"); + } + } + } + }, interval, interval); + } + } + + public boolean cleanExit(final boolean silent) { + if (silent) { + prefs.confirmations = true; + } + if (!prefs.confirmations) { + prefs.confirmations = true; + for (final Bot bot : bots) { + if (bot.getAccountName() != null) { + prefs.confirmations = true; + break; + } + } + } + boolean doExit = true; + if (!prefs.confirmations) { + final String message = "Are you sure you want to exit?"; + final int result = JOptionPane.showConfirmDialog(this, message, Messages.EXIT, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (result != JOptionPane.OK_OPTION) { + doExit = false; + } + } + try { + WebQueue.Destroy(); + } catch (NoClassDefFoundError ncdfe) { + } + setVisible(false); + try { + Monitoring.pushState(Type.ENVIRONMENT, "ADS", "SHOW", Boolean.toString(!prefs.ads)); + } catch (NoClassDefFoundError ncdfe) { + } + if (doExit) { + prefs.save(); + try { + Monitoring.stop(); + } catch (NoClassDefFoundError ncdfe) { + } + System.exit(0); + } else { + setVisible(true); + } + return doExit; + } + + public void setTray() { + if (tray == null) { + final Image image = Configuration.getImage(Configuration.Paths.Resources.ICON); + tray = new TrayIcon(image, Configuration.NAME, null); + tray.setImageAutoSize(true); + tray.addMouseListener(new MouseListener() { + public void mouseClicked(MouseEvent arg0) { + } + + public void mouseEntered(MouseEvent arg0) { + } + + public void mouseExited(MouseEvent arg0) { + } + + public void mouseReleased(MouseEvent arg0) { + } + + public void mousePressed(MouseEvent arg0) { + SystemTray.getSystemTray().remove(tray); + setVisible(true); + lessCpu(false); + } + }); + } + try { + SystemTray.getSystemTray().add(tray); + final String msg = "Bots are still running in the background.\nClick this icon to restore the window."; + tray.displayMessage(Configuration.NAME + " Hidden", msg, MessageType.INFO); + } catch (Exception ignored) { + log.warning("Unable to hide window"); + } + setVisible(false); + lessCpu(true); + } +} diff --git a/src/org/rsbot/gui/BotHome.java b/src/org/rsbot/gui/BotHome.java new file mode 100644 index 0000000..90ec411 --- /dev/null +++ b/src/org/rsbot/gui/BotHome.java @@ -0,0 +1,102 @@ +package org.rsbot.gui; + +import org.rsbot.bot.Bot; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Collection; + +/** + * @author Jacmob + * @author Paris + */ +public class BotHome { + private static final Font FONT = new Font("Helvetica", 1, 13); + private int width; + private int height; + private Bot[] bots = new Bot[0]; + + public void setBots(final Collection col) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + bots = col.toArray(new Bot[col.size()]); + } + }); + } + + public void setSize(final int width, final int height) { + this.width = width; + this.height = height; + } + + // TODO: Replace; temporary preview. + public void paint(final Graphics g) { + g.setColor(Color.black); + g.fillRect(0, 0, width, height); + final int len = Math.min(bots.length, BotGUI.MAX_BOTS); + switch (len) + { + case 1: + draw(g, 0, 0, 0, width, height); + break; + case 2: + draw(g, 0, 0, 0, width, height / 2); + draw(g, 1, 0, height / 2, width, height / 2); + break; + case 3: + draw(g, 0, 0, 0, width / 2, height / 2); + draw(g, 1, width / 2, 0, width / 2, height / 2); + draw(g, 2, 0, height / 2, width, height / 2); + break; + case 4: + draw(g, 0, 0, 0, width / 2, height / 2); + draw(g, 1, width / 2, 0, width / 2, height / 2); + draw(g, 2, 0, height / 2, width / 2, height / 2); + draw(g, 3, width / 2, height / 2, width / 2, height / 2); + break; + case 5: + draw(g, 0, 0, 0, width / 3, height / 2); + draw(g, 1, width / 3, 0, width / 3, height / 2); + draw(g, 2, width * 2 / 3, 0, width / 3, height / 2); + draw(g, 3, 0, height / 2, width / 2, height / 2); + draw(g, 4, width / 2, height / 2, width / 2, height / 2); + break; + case 6: + draw(g, 0, 0, 0, width / 3, height / 2); + draw(g, 1, width / 3, 0, width / 3, height / 2); + draw(g, 2, width * 2 / 3, 0, width / 3, height / 2); + draw(g, 3, 0, height / 2, width / 3, height / 2); + draw(g, 4, width / 3, height / 2, width / 3, height / 2); + draw(g, 5, width * 2 / 3, height / 2, width / 3, height / 2); + break; + default: + return; + } + final FontMetrics metrics = g.getFontMetrics(FONT); + g.setColor(new Color(0, 0, 0, 170)); + g.fillRect(0, height - 30, width, 30); + g.setColor(Color.white); + g.drawString("Spectating " + (bots.length == 1 ? "1 bot." : bots.length + " bots."), 5, height + metrics.getDescent() - 14); + } + + public void draw(final Graphics g, final int idx, final int x, final int y, final int width, final int height) { + final BufferedImage img = bots[idx].getImage(); + if (img != null && img.getWidth() > 0) { + final int w_img = img.getWidth(), h_img = img.getHeight(); + final float img_ratio = (float) w_img / (float) h_img; + final float bound_ratio = (float) width / (float) height; + int w, h; + if (img_ratio < bound_ratio) { + h = height; + w = (int) ((float) w_img / (float) h_img * h); + } else { + w = width; + h = (int) ((float) h_img / (float) w_img * w); + } + g.drawImage(img.getScaledInstance(w, h, Image.SCALE_SMOOTH), x + width / 2 - w / 2, y + height / 2 - h / 2, null); + g.setColor(Color.gray); + g.drawRect(x, y, width - 1, height - 1); + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/gui/BotMenuBar.java b/src/org/rsbot/gui/BotMenuBar.java new file mode 100644 index 0000000..ad73027 --- /dev/null +++ b/src/org/rsbot/gui/BotMenuBar.java @@ -0,0 +1,278 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.bot.Bot; +import org.rsbot.event.impl.*; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.event.listeners.TextPaintListener; +import org.rsbot.gui.component.Messages; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.util.*; +import java.util.List; +import java.util.Map.Entry; + +/** + * @author Paris + * @author Timer + */ +public class BotMenuBar extends JMenuBar { + private static final long serialVersionUID = 971579975301998332L; + public static final Map> DEBUG_MAP = new LinkedHashMap>(); + public static final String[] TITLES; + public static final String[][] ELEMENTS; + + private static final boolean EXTD_VIEW_INITIAL = !Configuration.RUNNING_FROM_JAR; + private static final String[] EXTD_VIEW_ITEMS = {"Game State", "Current Tab", "Camera", "Floor Height", + "Mouse Position", "User Input Allowed", "Menu", "Menu Actions", "Cache", "Models", "Calc Test", "Settings"}; + + static { + // Text + DEBUG_MAP.put("Game State", TLoginIndex.class); + DEBUG_MAP.put("Current Tab", TTab.class); + DEBUG_MAP.put("Camera", TCamera.class); + DEBUG_MAP.put("Animation", TAnimation.class); + DEBUG_MAP.put("Floor Height", TFloorHeight.class); + DEBUG_MAP.put("Player Position", TPlayerPosition.class); + DEBUG_MAP.put("Mouse Position", TMousePosition.class); + DEBUG_MAP.put("User Input Allowed", TUserInputAllowed.class); + DEBUG_MAP.put("Menu Actions", TMenuActions.class); + DEBUG_MAP.put("Menu", TMenu.class); + DEBUG_MAP.put("FPS", TFPS.class); + DEBUG_MAP.put("Cache", TWebStatus.class); + + // Paint + DEBUG_MAP.put("Players", DrawPlayers.class); + DEBUG_MAP.put("NPCs", DrawNPCs.class); + DEBUG_MAP.put("Objects", DrawObjects.class); + DEBUG_MAP.put("Models", DrawModel.class); + DEBUG_MAP.put("Mouse", DrawMouse.class); + DEBUG_MAP.put("Inventory", DrawInventory.class); + DEBUG_MAP.put("Ground Items", DrawItems.class); + DEBUG_MAP.put("Calc Test", DrawBoundaries.class); + DEBUG_MAP.put("Settings", DrawSettings.class); + DEBUG_MAP.put("Web", DrawWeb.class); + + // Other + DEBUG_MAP.put("Log Messages", MessageLogger.class); + + TITLES = new String[]{Messages.FILE, Messages.EDIT, Messages.VIEW, Messages.TOOLS, Messages.HELP}; + ELEMENTS = new String[][]{ + {Messages.NEWBOT, Messages.CLOSEBOT, Messages.MENUSEPERATOR, + Messages.ADDSCRIPT, + Messages.RUNSCRIPT, Messages.STOPSCRIPT, + Messages.PAUSESCRIPT, Messages.MENUSEPERATOR, + Messages.SAVESCREENSHOT, Messages.MENUSEPERATOR, + Messages.HIDEBOT, Messages.EXIT}, + {Messages.ACCOUNTS, Messages.MENUSEPERATOR, + Messages.TOGGLEFALSE + Messages.FORCEINPUT, + Messages.TOGGLEFALSE + Messages.LESSCPU, + (EXTD_VIEW_INITIAL ? Messages.TOGGLETRUE : Messages.TOGGLEFALSE) + Messages.EXTDVIEWS, + Messages.MENUSEPERATOR, + Messages.TOGGLEFALSE + Messages.DISABLEANTIRANDOMS, + Messages.TOGGLEFALSE + Messages.DISABLEAUTOLOGIN}, + constructDebugs(), {Messages.CLEARCACHE, Messages.OPTIONS}, {Messages.SITE, Messages.PROJECT, Messages.ABOUT}}; + } + + private static String[] constructDebugs() { + final List debugItems = new ArrayList(); + debugItems.add(Messages.HIDETOOLBAR); + debugItems.add(Messages.HIDELOGPANE); + debugItems.add(Messages.ALLDEBUGGING); + debugItems.add(Messages.MENUSEPERATOR); + for (final String key : DEBUG_MAP.keySet()) { + final Class el = DEBUG_MAP.get(key); + if (PaintListener.class.isAssignableFrom(el)) { + debugItems.add(key); + } + } + debugItems.add(Messages.MENUSEPERATOR); + for (final String key : DEBUG_MAP.keySet()) { + final Class el = DEBUG_MAP.get(key); + if (TextPaintListener.class.isAssignableFrom(el)) { + debugItems.add(key); + } + } + debugItems.add(Messages.MENUSEPERATOR); + for (final String key : DEBUG_MAP.keySet()) { + final Class el = DEBUG_MAP.get(key); + if (!TextPaintListener.class.isAssignableFrom(el) && !PaintListener.class.isAssignableFrom(el)) { + debugItems.add(key); + } + } + for (final ListIterator it = debugItems.listIterator(); it.hasNext();) { + final String s = it.next(); + if (!s.equals(Messages.MENUSEPERATOR)) { + it.set(Messages.TOGGLEFALSE + s); + } + } + return debugItems.toArray(new String[debugItems.size()]); + } + + private void constructItemIcons() { + final HashMap map = new HashMap(16); + map.put(Messages.NEWBOT, Configuration.Paths.Resources.ICON_APPADD); + map.put(Messages.CLOSEBOT, Configuration.Paths.Resources.ICON_APPDELETE); + map.put(Messages.ADDSCRIPT, Configuration.Paths.Resources.ICON_SCRIPT_ADD); + map.put(Messages.RUNSCRIPT, Configuration.Paths.Resources.ICON_PLAY); + map.put(Messages.STOPSCRIPT, Configuration.Paths.Resources.ICON_DELETE); + map.put(Messages.PAUSESCRIPT, Configuration.Paths.Resources.ICON_PAUSE); + map.put(Messages.SAVESCREENSHOT, Configuration.Paths.Resources.ICON_PHOTO); + map.put(Messages.HIDEBOT, Configuration.Paths.Resources.ICON_ARROWIN); + map.put(Messages.EXIT, Configuration.Paths.Resources.ICON_CLOSE); + map.put(Messages.ACCOUNTS, Configuration.Paths.Resources.ICON_REPORTKEY); + map.put(Messages.CLEARCACHE, Configuration.Paths.Resources.DATABASE_ERROR); + map.put(Messages.OPTIONS, Configuration.Paths.Resources.ICON_WRENCH); + map.put(Messages.SITE, Configuration.Paths.Resources.ICON_WEBLINK); + map.put(Messages.PROJECT, Configuration.Paths.Resources.ICON_GITHUB); + map.put(Messages.ABOUT, Configuration.Paths.Resources.ICON_INFO); + for (final Entry item : map.entrySet()) { + final JMenuItem menu = commandMenuItem.get(item.getKey()); + menu.setIcon(new ImageIcon(Configuration.getImage(item.getValue()))); + } + } + + private final Map eventCheckMap = new HashMap(); + private final Map commandCheckMap = new HashMap(); + private final Map commandMenuItem = new HashMap(); + private final ActionListener listener; + + public BotMenuBar(final ActionListener listener) { + this.listener = listener; + for (int i = 0; i < TITLES.length; i++) { + final String title = TITLES[i]; + final String[] elems = ELEMENTS[i]; + add(constructMenu(title, elems)); + } + constructItemIcons(); + commandMenuItem.get(Messages.HIDEBOT).setVisible(SystemTray.isSupported()); + setExtendedView(EXTD_VIEW_INITIAL); + } + + public void setExtendedView(final boolean show) { + for (String disableFeature : EXTD_VIEW_ITEMS) { + if (commandCheckMap.containsKey(disableFeature)) { + commandCheckMap.get(disableFeature).setVisible(show); + } + } + } + + public void setOverrideInput(final boolean force) { + commandCheckMap.get(Messages.FORCEINPUT).setSelected(force); + } + + public void setPauseScript(final boolean pause) { + final JMenuItem item = commandMenuItem.get(Messages.PAUSESCRIPT); + item.setText(pause ? Messages.RESUMESCRIPT : Messages.PAUSESCRIPT); + final Image image = Configuration.getImage(pause ? Configuration.Paths.Resources.ICON_START : Configuration.Paths.Resources.ICON_PAUSE); + if (image != null) { + item.setIcon(new ImageIcon(image)); + } + } + + public JMenuItem getMenuItem(final String name) { + return commandMenuItem.get(name); + } + + public void setBot(final Bot bot) { + if (bot == null) { + commandMenuItem.get(Messages.CLOSEBOT).setEnabled(false); + commandMenuItem.get(Messages.RUNSCRIPT).setEnabled(false); + commandMenuItem.get(Messages.STOPSCRIPT).setEnabled(false); + commandMenuItem.get(Messages.PAUSESCRIPT).setEnabled(false); + commandMenuItem.get(Messages.SAVESCREENSHOT).setEnabled(false); + for (final JCheckBoxMenuItem item : eventCheckMap.values()) { + item.setSelected(false); + item.setEnabled(false); + } + disable(Messages.ALLDEBUGGING, Messages.FORCEINPUT, Messages.LESSCPU, Messages.DISABLEANTIRANDOMS, Messages.DISABLEAUTOLOGIN); + } else { + commandMenuItem.get(Messages.CLOSEBOT).setEnabled(true); + commandMenuItem.get(Messages.RUNSCRIPT).setEnabled(true); + commandMenuItem.get(Messages.STOPSCRIPT).setEnabled(true); + commandMenuItem.get(Messages.PAUSESCRIPT).setEnabled(true); + commandMenuItem.get(Messages.SAVESCREENSHOT).setEnabled(true); + int selections = 0; + for (final Map.Entry entry : eventCheckMap.entrySet()) { + entry.getValue().setEnabled(true); + final boolean selected = bot.hasListener(DEBUG_MAP.get(entry.getKey())); + entry.getValue().setSelected(selected); + if (selected) { + ++selections; + } + } + enable(Messages.ALLDEBUGGING, selections == eventCheckMap.size()); + enable(Messages.FORCEINPUT, bot.overrideInput); + enable(Messages.LESSCPU, bot.disableRendering); + enable(Messages.DISABLEANTIRANDOMS, bot.disableRandoms); + enable(Messages.DISABLEAUTOLOGIN, bot.disableAutoLogin); + } + } + + public JCheckBoxMenuItem getCheckBox(final String key) { + return commandCheckMap.get(key); + } + + private void disable(final String... items) { + for (final String item : items) { + commandCheckMap.get(item).setSelected(false); + commandCheckMap.get(item).setEnabled(false); + } + } + + public void enable(final String item, final boolean selected) { + commandCheckMap.get(item).setSelected(selected); + commandCheckMap.get(item).setEnabled(true); + } + + public void setEnabled(final String item, final boolean mode) { + commandCheckMap.get(item).setEnabled(mode); + } + + public void doClick(final String item) { + commandMenuItem.get(item).doClick(); + } + + public void doTick(final String item) { + commandCheckMap.get(item).doClick(); + } + + public boolean isTicked(final String item) { + return commandCheckMap.get(item).isSelected(); + } + + private JMenu constructMenu(final String title, final String[] elems) { + final JMenu menu = new JMenu(title); + for (String e : elems) { + if (e.equals(Messages.MENUSEPERATOR)) { + menu.add(new JSeparator()); + } else { + JMenuItem jmi; + if (e.startsWith(Messages.TOGGLE)) { + e = e.substring(Messages.TOGGLE.length()); + final char state = e.charAt(0); + e = e.substring(2); + jmi = new JCheckBoxMenuItem(e); + if (state == 't' || state == 'T') { + jmi.setSelected(true); + } + if (DEBUG_MAP.containsKey(e)) { + final JCheckBoxMenuItem ji = (JCheckBoxMenuItem) jmi; + eventCheckMap.put(e, ji); + } + final JCheckBoxMenuItem ji = (JCheckBoxMenuItem) jmi; + commandCheckMap.put(e, ji); + } else { + jmi = new JMenuItem(e); + commandMenuItem.put(e, jmi); + } + jmi.addActionListener(listener); + jmi.setActionCommand(title + "." + e); + menu.add(jmi); + } + } + return menu; + } +} \ No newline at end of file diff --git a/src/org/rsbot/gui/BotPanel.java b/src/org/rsbot/gui/BotPanel.java new file mode 100644 index 0000000..584f6bf --- /dev/null +++ b/src/org/rsbot/gui/BotPanel.java @@ -0,0 +1,224 @@ +package org.rsbot.gui; + +import org.rsbot.bot.Bot; +import org.rsbot.event.EventManager; +import org.rsbot.script.methods.Mouse; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +import static org.rsbot.script.methods.Environment.INPUT_KEYBOARD; +import static org.rsbot.script.methods.Environment.INPUT_MOUSE; + +/** + * @author Jacmob + */ +public class BotPanel extends JPanel { + private static final long serialVersionUID = 2269767882075468055L; + + private class HomeUpdater implements Runnable { + private boolean running; + + public void run() { + synchronized (this) { + if (running) { + throw new IllegalStateException("Already running!"); + } + running = true; + } + while (true) { + synchronized (this) { + if (!running) { + break; + } + } + repaint(); + try { + Thread.sleep(70); + } catch (final Exception ex) { + break; + } + } + synchronized (this) { + running = false; + } + } + + public void stop() { + synchronized (this) { + running = false; + } + } + } + + private Bot bot; + private final BotHome home; + private final HomeUpdater updater; + private int offX; + private boolean present; + + public BotPanel(final BotHome home) { + this.home = home; + updater = new HomeUpdater(); + setSize(new Dimension(BotGUI.PANEL_WIDTH, BotGUI.PANEL_HEIGHT)); + setMinimumSize(new Dimension(BotGUI.PANEL_WIDTH, BotGUI.PANEL_HEIGHT)); + setPreferredSize(new Dimension(BotGUI.PANEL_WIDTH, BotGUI.PANEL_HEIGHT)); + setBackground(Color.black); + home.setSize(getWidth(), getHeight()); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(final ComponentEvent evt) { + BotPanel.this.home.setSize(getWidth(), getHeight()); + if (bot != null) { + bot.resize(getWidth(), getHeight()); + offset(); + } + requestFocus(); + } + }); + addMouseListener(new MouseListener() { + public void mouseClicked(final MouseEvent e) { + redispatch(e); + if (!hasFocus()) { + requestFocus(); + } + } + + public void mouseEntered(final MouseEvent e) { + } + + public void mouseExited(final MouseEvent e) { + redispatch(e); + } + + public void mousePressed(final MouseEvent e) { + redispatch(e); + } + + public void mouseReleased(final MouseEvent e) { + redispatch(e); + } + }); + addMouseMotionListener(new MouseMotionListener() { + public void mouseDragged(final MouseEvent e) { + redispatch(e); + } + + public void mouseMoved(final MouseEvent e) { + redispatch(e); + } + }); + addMouseWheelListener(new MouseWheelListener() { + public void mouseWheelMoved(final MouseWheelEvent e) { + redispatch(e); + } + }); + addKeyListener(new KeyListener() { + public void keyPressed(final KeyEvent e) { + redispatch(e); + } + + public void keyReleased(final KeyEvent e) { + redispatch(e); + } + + public void keyTyped(final KeyEvent e) { + redispatch(e); + } + }); + } + + public void offset() { + if (bot.getCanvas() != null) { + // center canvas horizontally if not filling container + offX = (getWidth() - bot.getCanvas().getWidth()) / 2; + } + } + + public void setBot(final Bot bot) { + if (this.bot != null) { + this.bot.setPanel(null); + } else { + updater.stop(); + } + this.bot = bot; + if (bot != null) { + bot.setPanel(this); + if (bot.getCanvas() != null) { + offset(); + } + } else { + new Thread(updater).start(); + } + } + + @Override + public void paintComponent(final Graphics g) { + super.paintComponent(g); + if (bot == null) { + home.paint(g); + } else { + g.drawImage(bot.getImage(), offX, 0, null); + } + } + + private void redispatch(final MouseEvent e) { + if (bot != null && bot.getLoader().getComponentCount() > 0) { + final Mouse mouse = bot.getMethodContext().mouse; + if (mouse == null) { + return; // client cannot currently accept events + } + final boolean present = mouse.isPresent(); + final Component c = bot.getLoader().getComponent(0); + // account for horizontal offset + e.translatePoint(-offX, 0); + // fire human mouse event for scripts + dispatchHuman(c, e); + if (!bot.overrideInput && (bot.inputFlags & INPUT_MOUSE) == 0) { + return; + } + if (e.getX() > 0 && e.getX() < c.getWidth() && e.getY() < c.getHeight() && e.getID() != MouseEvent.MOUSE_EXITED) { + if (present) { + if (e instanceof MouseWheelEvent) { + final MouseWheelEvent mwe = (MouseWheelEvent) e; + c.dispatchEvent(new MouseWheelEvent(c, e.getID(), System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, e.isPopupTrigger(), mwe.getScrollType(), mwe.getScrollAmount(), mwe.getWheelRotation())); + } else { + c.dispatchEvent(new MouseEvent(c, e.getID(), System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, e.isPopupTrigger(), e.getButton())); + } + } else { + c.dispatchEvent(new MouseEvent(c, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, false)); + } + } else if (present) { + c.dispatchEvent(new MouseEvent(c, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, false)); + } + } + } + + private void redispatch(final KeyEvent e) { + if (bot != null) { + final EventManager m = bot.getEventManager(); + if (m != null) { + m.dispatchEvent(e); + } + if ((bot.overrideInput || (bot.inputFlags & INPUT_KEYBOARD) != 0) && bot.getLoader().getComponentCount() > 0) { + final Component c = bot.getLoader().getComponent(0); + c.dispatchEvent(e); + } + } + } + + private void dispatchHuman(final Component c, final MouseEvent e) { + if (e.getX() > 0 && e.getX() < c.getWidth() && e.getY() < c.getHeight() && e.getID() != MouseEvent.MOUSE_EXITED) { + if (present) { + bot.getEventManager().dispatchEvent(e); + } else { + present = true; + bot.getEventManager().dispatchEvent(new MouseEvent(c, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, false)); + } + } else if (present) { + present = false; + bot.getEventManager().dispatchEvent(new MouseEvent(c, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, e.getX(), e.getY(), 0, false)); + } + } +} \ No newline at end of file diff --git a/src/org/rsbot/gui/BotToolBar.java b/src/org/rsbot/gui/BotToolBar.java new file mode 100644 index 0000000..8ff6bd3 --- /dev/null +++ b/src/org/rsbot/gui/BotToolBar.java @@ -0,0 +1,469 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.gui.component.Messages; +import org.rsbot.script.methods.Environment; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * @author Paris + */ +public class BotToolBar extends JToolBar { + + private static final long serialVersionUID = -1861866523519184211L; + + public static final int RUN_SCRIPT = 0; + public static final int PAUSE_SCRIPT = 1; + public static final int RESUME_SCRIPT = 2; + + public static final ImageIcon ICON_HOME; + public static final ImageIcon ICON_BOT; + + public static Image IMAGE_CLOSE; + public static final Image IMAGE_CLOSE_OVER; + + private static final int TABINDEX = 1; + private static final int BUTTONCOUNT = 6; + private static final int OPTIONBUTTONS = 4; + + static { + ICON_HOME = new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_HOME)); + ICON_BOT = new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_BOT)); + IMAGE_CLOSE_OVER = Configuration.getImage(Configuration.Paths.Resources.ICON_CLOSE); + } + + private final AddButton addTabButton; + private final JButton screenshotButton; + private final JButton userInputButton; + private final JButton runScriptButton; + private final JButton stopScriptButton; + + private final ActionListener listener; + private int idx; + private int inputState = Environment.INPUT_KEYBOARD | Environment.INPUT_MOUSE; + private boolean inputOverride = true; + + public BotToolBar(final ActionListener listener, final BotMenuBar menu) { + try { + IMAGE_CLOSE = getTransparentImage(Configuration.getResourceURL(Configuration.Paths.Resources.ICON_CLOSE), 0.5f); + } catch (final MalformedURLException e) { + } + + this.listener = listener; + + screenshotButton = new JButton("Screenshot", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_PHOTO))); + screenshotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + menu.doClick(Messages.SAVESCREENSHOT); + } + }); + screenshotButton.setFocusable(false); + screenshotButton.setToolTipText(screenshotButton.getText()); + screenshotButton.setText(""); + + stopScriptButton = new JButton("Stop", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_DELETE))); + stopScriptButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + menu.doClick(Messages.STOPSCRIPT); + } + }); + stopScriptButton.setFocusable(false); + stopScriptButton.setToolTipText(stopScriptButton.getText()); + stopScriptButton.setText(""); + + userInputButton = new JButton("Input", new ImageIcon(getInputImage(inputOverride, inputState))); + userInputButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + menu.doTick(Messages.FORCEINPUT); + } + }); + userInputButton.setFocusable(false); + userInputButton.setToolTipText(userInputButton.getText()); + userInputButton.setText(""); + + runScriptButton = new JButton("Run", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_PLAY))); + runScriptButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + switch (getScriptButton()) { + case RUN_SCRIPT: + menu.doClick(Messages.RUNSCRIPT); + break; + case RESUME_SCRIPT: + case PAUSE_SCRIPT: + menu.doClick(Messages.PAUSESCRIPT); + break; + } + } + }); + runScriptButton.setFocusable(false); + runScriptButton.setToolTipText(runScriptButton.getText()); + runScriptButton.setText(""); + + final HomeButton home = new HomeButton(ICON_HOME); + + setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); + setFloatable(false); + add(home); + add(addTabButton = new AddButton(listener)); + add(Box.createHorizontalGlue()); + add(screenshotButton); + add(runScriptButton); + add(stopScriptButton); + add(userInputButton); + updateSelection(false); + } + + public void setAddTabVisible(final boolean visible) { + addTabButton.setVisible(visible); + } + + public void setInputButtonVisible(final boolean visible) { + userInputButton.setVisible(visible); + } + + public void addTab() { + final int idx = getComponentCount() - BUTTONCOUNT - TABINDEX + 1; + add(new BotButton(Messages.TABDEFAULTTEXT, ICON_BOT), idx); + validate(); + setSelection(idx); + } + + public void removeTab(int idx) { + final int current = getCurrentTab() + TABINDEX; + final int select = idx == current ? idx - TABINDEX : current; + idx += TABINDEX; + remove(idx); + revalidate(); + repaint(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setSelection(Math.max(0, select - 1)); + } + }); + } + + public void setTabLabel(final int idx, final String label) { + ((BotButton) getComponentAtIndex(idx + TABINDEX)).setText(label); + } + + public int getCurrentTab() { + if (idx > -1 && idx < getComponentCount() - OPTIONBUTTONS) { + return idx - TABINDEX; + } else { + return -1; + } + } + + public int getScriptButton() { + final String label = runScriptButton.getToolTipText(); + if (label.equals("Run")) { + return RUN_SCRIPT; + } else if (label.equals("Pause")) { + return PAUSE_SCRIPT; + } else if (label.equals("Resume")) { + return RESUME_SCRIPT; + } else { + throw new IllegalStateException("Illegal script button state!"); + } + } + + public void setHome(final boolean home) { + for (final JButton button : new JButton[]{screenshotButton, stopScriptButton, userInputButton, runScriptButton}) { + button.setEnabled(!home); + button.setVisible(!home); + } + } + + public void setInputState(final int state) { + inputState = state; + } + + public void setOverrideInput(final boolean selected) { + inputOverride = selected; + } + + public void updateInputButton() { + userInputButton.setIcon(new ImageIcon(getInputImage(inputOverride, inputState))); + } + + public void setScriptButton(final int state) { + String text = null, pathResource = null; + boolean running = true; + + switch (state) { + case RUN_SCRIPT: + text = "Run"; + pathResource = Configuration.Paths.Resources.ICON_PLAY; + running = false; + break; + case PAUSE_SCRIPT: + text = "Pause"; + pathResource = Configuration.Paths.Resources.ICON_PAUSE; + break; + case RESUME_SCRIPT: + text = "Resume"; + pathResource = Configuration.Paths.Resources.ICON_START; + break; + } + + stopScriptButton.setVisible(running); + runScriptButton.setToolTipText(text); + runScriptButton.setIcon(new ImageIcon(Configuration.getImage(pathResource))); + runScriptButton.repaint(); + revalidate(); + } + + private void setSelection(final int idx) { + updateSelection(true); + this.idx = idx; + updateSelection(false); + listener.actionPerformed(new ActionEvent(this, 0, "Tab")); + } + + private void updateSelection(final boolean enabled) { + final int idx = getCurrentTab() + TABINDEX; + if (idx >= 0) { + getComponent(idx).setEnabled(enabled); + getComponent(idx).repaint(); + } + } + + private Image getInputImage(final boolean override, final int state) { + if (override || state == (Environment.INPUT_KEYBOARD | Environment.INPUT_MOUSE)) { + return Configuration.getImage(Configuration.Paths.Resources.ICON_TICK); + } else if (state == Environment.INPUT_KEYBOARD) { + return Configuration.getImage(Configuration.Paths.Resources.ICON_KEYBOARD); + } else if (state == Environment.INPUT_MOUSE) { + return Configuration.getImage(Configuration.Paths.Resources.ICON_MOUSE); + } else { + return Configuration.getImage(Configuration.Paths.Resources.ICON_DELETE); + } + } + + private static Image getTransparentImage(final URL url, final float transparency) { + BufferedImage loaded = null; + try { + loaded = ImageIO.read(url); + } catch (final IOException e) { + } + final BufferedImage aimg = new BufferedImage(loaded.getWidth(), loaded.getHeight(), Transparency.TRANSLUCENT); + final Graphics2D g = aimg.createGraphics(); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency)); + g.drawImage(loaded, null, 0, 0); + g.dispose(); + return aimg; + } + + /** + * @author Jacmob + */ + private class HomeButton extends JPanel { + + private static final long serialVersionUID = 938456324328L; + + private final Image image; + private boolean hovered; + + public HomeButton(final ImageIcon icon) { + super(new BorderLayout()); + image = icon.getImage(); + setBorder(new EmptyBorder(3, 6, 2, 3)); + setPreferredSize(new Dimension(24, 22)); + setMaximumSize(new Dimension(24, 22)); + setFocusable(false); + addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(final MouseEvent e) { + setSelection(getComponentIndex(HomeButton.this)); + } + + @Override + public void mouseEntered(final MouseEvent e) { + hovered = true; + repaint(); + } + + @Override + public void mouseExited(final MouseEvent e) { + hovered = false; + repaint(); + } + }); + } + + @Override + public void paintComponent(final Graphics g) { + super.paintComponent(g); + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + if (getComponentIndex(this) == idx) { + g.setColor(new Color(255, 255, 255, 200)); + g.fillRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + g.setColor(new Color(180, 180, 180, 200)); + g.drawRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + } else if (hovered) { + g.setColor(new Color(255, 255, 255, 150)); + g.fillRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + g.setColor(new Color(180, 180, 180, 150)); + g.drawRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + } + g.drawImage(image, 3, 3, null); + } + + } + + /** + * @author Tekk + */ + private class BotButton extends JPanel { + + private static final long serialVersionUID = 329845763420L; + + private final JLabel nameLabel; + private boolean hovered; + private boolean close; + + public BotButton(final String text, final Icon icon) { + super(new BorderLayout()); + setBorder(new EmptyBorder(3, 6, 2, 3)); + nameLabel = new JLabel(text); + nameLabel.setIcon(icon); + nameLabel.setPreferredSize(new Dimension(85, 22)); + nameLabel.setMaximumSize(new Dimension(85, 22)); + add(nameLabel, BorderLayout.WEST); + + setPreferredSize(new Dimension(110, 22)); + setMaximumSize(new Dimension(110, 22)); + setFocusable(false); + addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(final MouseEvent e) { + if (hovered && close) { + final int idx = getComponentIndex(BotButton.this) - TABINDEX; + listener.actionPerformed(new ActionEvent(this, + ActionEvent.ACTION_PERFORMED, Messages.CLOSEBOT + "." + idx)); + } else { + setSelection(getComponentIndex(BotButton.this)); + } + } + + @Override + public void mouseEntered(final MouseEvent e) { + hovered = true; + repaint(); + } + + @Override + public void mouseExited(final MouseEvent e) { + hovered = false; + repaint(); + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(final MouseEvent e) { + close = e.getX() > 95; + repaint(); + } + }); + } + + public void setText(final String label) { + nameLabel.setText(label); + } + + @Override + public void paintComponent(final Graphics g) { + super.paintComponent(g); + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + final int RGB = getComponentIndex(this) == idx ? 255 : hovered ? 230 : 215; + g.setColor(new Color(RGB, RGB, RGB, 200)); + g.fillRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + g.setColor(new Color(180, 180, 180, 200)); + g.drawRoundRect(0, 0, getWidth() - 2, getHeight() - 1, 4, 4); + g.drawImage(hovered && close ? IMAGE_CLOSE_OVER : IMAGE_CLOSE, 90, 3, null); + } + } + + private static class AddButton extends JComponent { + + private static final long serialVersionUID = 1L; + + private static Image ICON; + private static Image ICON_OVER; + private static final Image ICON_DOWN; + private boolean hovered = false; + private boolean pressed = false; + + static { + ICON_DOWN = Configuration.getImage(Configuration.Paths.Resources.ICON_ADD); + } + + public AddButton(final ActionListener listener) { + URL src = null; + try { + src = Configuration.getResourceURL(Configuration.Paths.Resources.ICON_ADD); + } catch (final MalformedURLException e) { + } + ICON = getTransparentImage(src, 0.3f); + ICON_OVER = getTransparentImage(src, 0.7f); + + setPreferredSize(new Dimension(20, 20)); + setMaximumSize(new Dimension(20, 20)); + setFocusable(false); + addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(final MouseEvent e) { + hovered = true; + repaint(); + } + + @Override + public void mouseExited(final MouseEvent e) { + hovered = false; + repaint(); + } + + @Override + public void mousePressed(final MouseEvent e) { + pressed = true; + repaint(); + } + + @Override + public void mouseReleased(final MouseEvent e) { + pressed = false; + repaint(); + listener.actionPerformed(new ActionEvent(this, e.getID(), "File.New Bot")); + } + }); + } + + @Override + public void paintComponent(final Graphics g) { + super.paintComponent(g); + if (pressed) { + g.drawImage(ICON_DOWN, 2, 2, null); + } else if (hovered) { + g.drawImage(ICON_OVER, 2, 2, null); + } else { + g.drawImage(ICON, 2, 2, null); + } + } + + } + +} \ No newline at end of file diff --git a/src/org/rsbot/gui/ScriptSelector.java b/src/org/rsbot/gui/ScriptSelector.java new file mode 100644 index 0000000..2e15ecd --- /dev/null +++ b/src/org/rsbot/gui/ScriptSelector.java @@ -0,0 +1,483 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.bot.Bot; +import org.rsbot.gui.component.JComboCheckBox; +import org.rsbot.script.Script; +import org.rsbot.script.internal.ScriptHandler; +import org.rsbot.script.internal.event.ScriptListener; +import org.rsbot.script.provider.FileScriptSource; +import org.rsbot.script.provider.ScriptDefinition; +import org.rsbot.script.provider.ScriptDeliveryNetwork; +import org.rsbot.script.provider.ScriptSource; +import org.rsbot.service.ServiceException; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; + +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +/** + * @author Paris + * @author Jacmob + */ +public class ScriptSelector extends JDialog implements ScriptListener { + private static final long serialVersionUID = 5475451138208522511L; + private static final Logger log = Logger.getLogger(ScriptSelector.class.getName()); + private static final String[] COLUMN_NAMES = new String[]{"", "Name", "Description"}; + + private static final ScriptSource SRC_SOURCES; + private static final ScriptSource SRC_PRECOMPILED; + private static final ScriptSource SRC_NETWORK; + private final BotGUI frame; + private final Bot bot; + private JTable table; + private JTextField search; + private JComboBox accounts; + private final JComboCheckBox categories = new JComboCheckBox(); + private final ScriptTableModel model; + private final List scripts; + private JButton submit; + private boolean connected = true; + + static { + SRC_SOURCES = new FileScriptSource(new File(Configuration.Paths.getScriptsSourcesDirectory())); + SRC_PRECOMPILED = new FileScriptSource(new File(Configuration.Paths.getScriptsPrecompiledDirectory())); + SRC_NETWORK = ScriptDeliveryNetwork.getInstance(); + } + + public ScriptSelector(final BotGUI frame, final Bot bot) { + super(frame, "Script Selector", true); + this.frame = frame; + this.bot = bot; + scripts = new ArrayList(); + model = new ScriptTableModel(scripts); + } + + public void showGUI() { + init(); + update(); + load(); + setVisible(true); + } + + public void update() { + final boolean available = bot.getScriptHandler().getRunningScripts().size() == 0; + submit.setEnabled(available && table.getSelectedRow() != -1); + table.setEnabled(available); + search.setEnabled(available); + accounts.setEnabled(available); + table.clearSelection(); + } + + private void load() { + scripts.clear(); + if (connected) { + final List net = SRC_NETWORK.list(); + if (net != null) { + scripts.addAll(net); + } + } + scripts.addAll(SRC_PRECOMPILED.list()); + scripts.addAll(SRC_SOURCES.list()); + Collections.sort(scripts); + + final ArrayList keywords = new ArrayList(scripts.size()); + for (final ScriptDefinition def : scripts) { + if (def.keywords == null || def.keywords.length == 0) { + continue; + } + for (String keyword : def.keywords) { + keyword = keyword.toLowerCase().trim(); + if (keyword.length() > 0 && !keywords.contains(keyword)) { + keywords.add(keyword); + } + } + } + Collections.sort(keywords); + categories.populate(keywords, false); + + filter(); + table.revalidate(); + } + + private void init() { + setIconImage(Configuration.getImage(Configuration.Paths.Resources.ICON_SCRIPT)); + setLayout(new BorderLayout()); + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + bot.getScriptHandler().addScriptListener(ScriptSelector.this); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent e) { + bot.getScriptHandler().removeScriptListener(ScriptSelector.this); + dispose(); + } + }); + final Color searchAltColor = Color.GRAY; + final JButton refresh = new JButton(new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_REFRESH))); + refresh.setToolTipText("Refresh"); + refresh.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + refresh.setEnabled(false); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new Thread() { + @Override + public void run() { + ScriptDeliveryNetwork.getInstance().refresh(true); + load(); + refresh.setEnabled(true); + } + }.start(); + } + }); + } + }); + table = new JTable(model) { + private static final long serialVersionUID = 6969410339933692133L; + + @Override + public String getToolTipText(MouseEvent e) { + int row = rowAtPoint(e.getPoint()); + ScriptDefinition def = model.getDefinition(row); + return def.toString(); + } + }; + table.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) { + final int row = table.rowAtPoint(e.getPoint()); + table.getSelectionModel().setSelectionInterval(row, row); + showMenu(e); + } + } + + private void showMenu(final MouseEvent e) { + final int row = table.rowAtPoint(e.getPoint()); + final ScriptDefinition def = model.getDefinition(row); + + final JPopupMenu contextMenu = new JPopupMenu(); + final JMenuItem visit = new JMenuItem(); + visit.setText("Visit Site"); + visit.setIcon(new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_WEBLINK))); + visit.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + BotGUI.openURL(def.website); + } + }); + + final JMenuItem start = new JMenuItem(); + start.setText(submit.getText()); + start.setIcon(new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_PLAY))); + start.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + submit.doClick(); + } + }); + start.setEnabled(submit.isEnabled()); + + final JMenuItem delete = new JMenuItem(); + delete.setText("Delete"); + delete.setIcon(new ImageIcon(Configuration.getImage(Configuration.Paths.Resources.ICON_CLOSE))); + delete.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final File path = def.path == null || def.path.isEmpty() ? null : new File(def.path); + if (path != null && path.exists() && path.delete()) { + log.info("Deleted script " + def.name + " (" + def.path + ")"); + } else { + log.warning("Could not delete " + def.name); + } + scripts.remove(def); + load(); + } + }); + + if (def.website == null || def.website.isEmpty()) { + visit.setEnabled(false); + } + + contextMenu.add(start); + contextMenu.add(visit); + contextMenu.add(delete); + contextMenu.show(table, e.getX(), e.getY()); + } + }); + table.setRowHeight(20); + table.setIntercellSpacing(new Dimension(1, 1)); + table.setShowGrid(false); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.getSelectionModel().addListSelectionListener(new TableSelectionListener()); + setColumnWidths(table, 30, 200); + final JToolBar toolBar = new JToolBar(); + toolBar.setMargin(new Insets(1, 1, 1, 1)); + toolBar.setFloatable(false); + search = new JTextField(); + final Color searchDefaultColor = search.getForeground(); + final String searchDefaultText = "Type to filter...\0"; + search.setText(searchDefaultText); + search.setForeground(searchAltColor); + search.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(final FocusEvent e) { + if (search.getForeground() == searchAltColor) { + search.setText(""); + search.setForeground(searchDefaultColor); + } + table.clearSelection(); + } + + @Override + public void focusLost(final FocusEvent e) { + if (search.getText().isEmpty()) { + search.setText(searchDefaultText); + search.setForeground(searchAltColor); + } + } + }); + search.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(final KeyEvent e) { + filter(); + table.revalidate(); + } + + @Override + public void keyReleased(final KeyEvent e) { + keyTyped(e); + } + }); + submit = new JButton("Start", new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_PLAY))); + final JButton connect = new JButton(new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_CONNECT))); + connect.setToolTipText("Show network scripts"); + submit.setEnabled(false); + submit.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent evt) { + final ScriptDefinition def = model.getDefinition(table.getSelectedRow()); + setVisible(false); + final String account = (String) accounts.getSelectedItem(); + bot.getScriptHandler().removeScriptListener(ScriptSelector.this); + dispose(); + new Thread() { + @Override + public void run() { + Script script = null; + frame.updateScriptControls(true); + try { + script = def.source.load(def); + } catch (final ServiceException e) { + log.severe(e.getMessage()); + } + if (script != null) { + bot.setAccount(account); + bot.getScriptHandler().runScript(script); + frame.updateScriptControls(); + } + } + }.start(); + } + }); + if (connect.isEnabled()) { + final ActionListener listenConnect = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final String icon = connected ? Configuration.Paths.Resources.ICON_DISCONNECT : + Configuration.Paths.Resources.ICON_CONNECT; + connect.setIcon(new ImageIcon(Configuration.getImage(icon))); + connect.repaint(); + connected = !connected; + load(); + } + }; + connect.addActionListener(listenConnect); + } + accounts = new JComboBox(AccountManager.getAccountNames()); + accounts.setPreferredSize(new Dimension(125, 20)); + categories.setPreferredSize(new Dimension(150, 20)); + categories.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent arg0) { + final String[] selected = categories.getSelectedItems(); + final StringBuilder s = new StringBuilder(16); + switch (selected.length) { + case 0: + s.append("Showing all"); + break; + case 1: + s.append(selected[0]); + break; + case 2: + s.append(selected[0]); + s.append(" & "); + s.append(selected[1]); + break; + default: + s.append("Showing "); + s.append(selected.length); + s.append(" types"); + break; + } + categories.setText(s.toString()); + filter(); + } + }); + toolBar.add(search); + toolBar.add(Box.createHorizontalStrut(5)); + toolBar.add(categories); + toolBar.add(Box.createHorizontalStrut(5)); + toolBar.add(accounts); + toolBar.add(Box.createHorizontalStrut(5)); + toolBar.add(refresh); + toolBar.add(Box.createHorizontalStrut(5)); + toolBar.add(connect); + toolBar.add(Box.createHorizontalStrut(5)); + toolBar.add(submit); + final JPanel center = new JPanel(); + center.setLayout(new BorderLayout()); + final JScrollPane pane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + center.add(pane, BorderLayout.CENTER); + add(center, BorderLayout.CENTER); + add(toolBar, BorderLayout.SOUTH); + setSize(750, 400); + setMinimumSize(getSize()); + setLocationRelativeTo(getParent()); + search.requestFocus(); + } + + private void filter() { + model.search((search == null || search.getText().contains("\0")) ? "" : search.getText(), categories.getSelectedItems()); + } + + private void setColumnWidths(final JTable table, final int... widths) { + for (int i = 0; i < widths.length; ++i) { + final TableColumn col = table.getColumnModel().getColumn(i); + col.setPreferredWidth(widths[i]); + col.setMinWidth(widths[i]); + col.setMaxWidth(widths[i]); + } + } + + public void scriptStarted(final ScriptHandler handler, final Script script) { + update(); + } + + public void scriptStopped(final ScriptHandler handler, final Script script) { + update(); + } + + public void scriptResumed(final ScriptHandler handler, final Script script) { + } + + public void scriptPaused(final ScriptHandler handler, final Script script) { + } + + public void inputChanged(final Bot bot, final int mask) { + } + + private class TableSelectionListener implements ListSelectionListener { + public void valueChanged(final ListSelectionEvent evt) { + if (!evt.getValueIsAdjusting()) { + submit.setEnabled(table.getSelectedRow() != -1); + } + } + } + + private static class ScriptTableModel extends AbstractTableModel { + private static final long serialVersionUID = 1L; + public static final ImageIcon ICON_SCRIPT_SRC = new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_SCRIPT_EDIT)); + public static final ImageIcon ICON_SCRIPT_PRE = new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_SCRIPT_GEAR)); + public static final ImageIcon ICON_SCRIPT_NET = new ImageIcon( + Configuration.getImage(Configuration.Paths.Resources.ICON_SCRIPT_LIVE)); + private final List scripts; + private final List matches; + + public ScriptTableModel(final List scripts) { + this.scripts = scripts; + matches = new ArrayList(); + } + + public void search(final String find, final String[] keys) { + matches.clear(); + for (final ScriptDefinition def : scripts) { + if (find.length() != 0 && !def.name.toLowerCase().contains(find)) { + continue; + } + final ArrayList list = new ArrayList(def.keywords.length); + for (final String key : def.keywords) { + list.add(key.toLowerCase()); + } + boolean hit = true; + for (final String key : keys) { + if (!list.contains(key)) { + hit = false; + break; + } + } + if (hit) { + matches.add(def); + } + } + fireTableDataChanged(); + } + + public ScriptDefinition getDefinition(final int rowIndex) { + return matches.get(rowIndex); + } + + public int getRowCount() { + return matches.size(); + } + + public int getColumnCount() { + return COLUMN_NAMES.length; + } + + public Object getValueAt(final int rowIndex, final int columnIndex) { + if (rowIndex >= 0 && rowIndex < matches.size()) { + final ScriptDefinition def = matches.get(rowIndex); + switch (columnIndex) { + case 0: + if (def.source == SRC_SOURCES) { + return ICON_SCRIPT_SRC; + } + if (def.source == SRC_PRECOMPILED) { + return ICON_SCRIPT_PRE; + } + return ICON_SCRIPT_NET; + case 1: + return def.getName(); + case 2: + return def.getDescription(); + } + } + return null; + } + + @Override + public Class getColumnClass(final int col) { + if (col == 0) { + return ImageIcon.class; + } + return String.class; + } + + @Override + public String getColumnName(final int col) { + return COLUMN_NAMES[col]; + } + } +} diff --git a/src/org/rsbot/gui/SettingsManager.java b/src/org/rsbot/gui/SettingsManager.java new file mode 100644 index 0000000..0fb11a0 --- /dev/null +++ b/src/org/rsbot/gui/SettingsManager.java @@ -0,0 +1,335 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.Configuration.OperatingSystem; +import org.rsbot.gui.component.Messages; +import org.rsbot.service.Monitoring; +import org.rsbot.service.DRM; +import org.rsbot.util.StringUtil; +import org.rsbot.util.io.IniParser; + +import javax.swing.*; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * @author Paris + */ +public class SettingsManager extends JDialog { + private static Logger log = Logger.getLogger(SettingsManager.class.getName()); + private static final long serialVersionUID = 1657935322078534422L; + private static final String DEFAULTPASSWORD = "\0\0\0\0\0\0\0\0"; + private Preferences prefs; + + public class Preferences { + private File store; + + /** + * Whether or not to disable ads. + */ + public boolean ads = true; + public String user = ""; + public boolean confirmations = true; + public boolean monitoring = true; + public boolean shutdown = false; + public int shutdownTime = 10; + public boolean web = false; + public String webBind = "localhost:9500"; + public boolean webPassRequire = false; + public String webPass = ""; + + public Preferences(final File store) { + this.store = store; + } + + public void load() { + HashMap keys = null; + try { + if (!store.exists()) { + store.createNewFile(); + } + keys = IniParser.deserialise(store).get(IniParser.emptySection); + } catch (final IOException ignored) { + log.severe("Failed to load preferences"); + } + if (keys == null || keys.isEmpty()) { + return; + } + if (keys.containsKey("user")) { + user = keys.get("user"); + } + if (keys.containsKey("ads")) { + ads = IniParser.parseBool(keys.get("ads")); + } + if (keys.containsKey("confirmations")) { + confirmations = IniParser.parseBool(keys.get("confirmations")); + } + if (keys.containsKey("monitoring")) { + monitoring = IniParser.parseBool(keys.get("ads")); + } + if (keys.containsKey("shutdown")) { + shutdown = IniParser.parseBool(keys.get("shutdown")); + } + if (keys.containsKey("shutdownTime")) { + shutdownTime = Integer.parseInt(keys.get("shutdownTime")); + shutdownTime = Math.max(Math.min(shutdownTime, 60), 3); + } + if (keys.containsKey("web")) { + web = IniParser.parseBool(keys.get("web")); + } + if (keys.containsKey("webBind")) { + webBind = keys.get("webBind"); + } + if (keys.containsKey("webPassRequire")) { + webPassRequire = IniParser.parseBool(keys.get("webPassRequire")); + } + if (keys.containsKey("webPass")) { + webPass = keys.get("webPass"); + } + } + + public void save() { + final HashMap keys = new HashMap(5); + keys.put("user", user); + keys.put("ads", Boolean.toString(ads)); + keys.put("confirmations", Boolean.toString(confirmations)); + keys.put("monitoring", Boolean.toString(monitoring)); + keys.put("shutdown", Boolean.toString(shutdown)); + keys.put("shutdownTime", Integer.toString(shutdownTime)); + keys.put("web", Boolean.toString(web)); + keys.put("webBind", webBind); + keys.put("webPassRequire", Boolean.toString(webPassRequire)); + keys.put("webPass", webPass); + final HashMap> data = new HashMap>(1); + data.put(IniParser.emptySection, keys); + try { + final BufferedWriter out = new BufferedWriter(new FileWriter(store)); + IniParser.serialise(data, out); + out.close(); + } catch (final IOException ignored) { + log.severe("Could not save preferences"); + } + } + + public void commit() { + Monitoring.setEnabled(monitoring); + } + } + + public Preferences getPreferences() { + return prefs; + } + + public SettingsManager(final Frame owner, final File store) { + super(owner, Messages.OPTIONS, true); + prefs = new Preferences(store); + prefs.load(); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + setIconImage(Configuration.getImage(Configuration.Paths.Resources.ICON_WRENCH)); + + final JPanel panelLogin = new JPanel(new GridLayout(2, 1)); + panelLogin.setBorder(BorderFactory.createTitledBorder("Service Login")); + final JPanel panelOptions = new JPanel(new GridLayout(0, 1)); + panelOptions.setBorder(BorderFactory.createTitledBorder("Display")); + final JPanel panelInternal = new JPanel(new GridLayout(0, 1)); + panelInternal.setBorder(BorderFactory.createTitledBorder("Internal")); + final JPanel panelWeb = new JPanel(new GridLayout(2, 1)); + panelWeb.setBorder(BorderFactory.createTitledBorder("Web UI")); + + final JPanel[] panelLoginOptions = new JPanel[2]; + for (int i = 0; i < panelLoginOptions.length; i++) { + panelLoginOptions[i] = new JPanel(new GridLayout(1, 2)); + } + panelLoginOptions[0].add(new JLabel(" Username:")); + final JTextField textLoginUser = new JTextField(prefs.user); + textLoginUser.setToolTipText(Configuration.Paths.URLs.HOST + " forum account username, leave blank to log out"); + panelLoginOptions[0].add(textLoginUser); + panelLoginOptions[1].add(new JLabel(" Password:")); + final JPasswordField textLoginPass = new JPasswordField(prefs.user.length() == 0 ? "" : DEFAULTPASSWORD); + panelLoginOptions[1].add(textLoginPass); + panelLogin.add(panelLoginOptions[0]); + panelLogin.add(panelLoginOptions[1]); + + final JCheckBox checkAds = new JCheckBox(Messages.DISABLEADS); + checkAds.setToolTipText("Show advertisment on startup"); + checkAds.setSelected(prefs.ads); + + final JCheckBox checkConf = new JCheckBox(Messages.DISABLECONFIRMATIONS); + checkConf.setToolTipText("Supress confirmation messages"); + checkConf.setSelected(prefs.confirmations); + + final JCheckBox checkMonitor = new JCheckBox(Messages.DISABLEMONITORING); + checkMonitor.setToolTipText("Monitor system information to improve development"); + checkMonitor.setSelected(prefs.monitoring); + + final JPanel panelShutdown = new JPanel(new GridLayout(1, 2)); + final JCheckBox checkShutdown = new JCheckBox(Messages.AUTOSHUTDOWN); + checkShutdown.setToolTipText("Automatic system shutdown after specified period of inactivty"); + checkShutdown.setSelected(prefs.shutdown); + panelShutdown.add(checkShutdown); + final SpinnerNumberModel modelShutdown = new SpinnerNumberModel(prefs.shutdownTime, 3, 60, 1); + final JSpinner valueShutdown = new JSpinner(modelShutdown); + panelShutdown.add(valueShutdown); + checkShutdown.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent arg0) { + valueShutdown.setEnabled(((JCheckBox) arg0.getSource()).isSelected()); + } + }); + checkShutdown.setEnabled(Configuration.getCurrentOperatingSystem() == OperatingSystem.WINDOWS); + valueShutdown.setEnabled(checkShutdown.isEnabled() && checkShutdown.isSelected()); + + final JPanel[] panelWebOptions = new JPanel[2]; + for (int i = 0; i < panelWebOptions.length; i++) { + panelWebOptions[i] = new JPanel(new GridLayout(1, 2)); + } + final JCheckBox checkWeb = new JCheckBox(Messages.BINDTO); + checkWeb.setToolTipText("Remote control via web interface"); + checkWeb.setSelected(prefs.web); + panelWebOptions[0].add(checkWeb); + final JFormattedTextField textWebBind = new JFormattedTextField(prefs.webBind); + textWebBind.setToolTipText("Example: localhost:9500"); + panelWebOptions[0].add(textWebBind); + final JCheckBox checkWebPass = new JCheckBox(Messages.USEPASSWORD); + checkWebPass.setSelected(prefs.webPassRequire); + panelWebOptions[1].add(checkWebPass); + final JPasswordField textWebPass = new JPasswordField(DEFAULTPASSWORD); + textWebPass.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(final FocusEvent e) { + textWebPass.setText(""); + } + }); + panelWebOptions[1].add(textWebPass); + checkWebPass.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textWebPass.setEnabled(checkWebPass.isSelected() && checkWebPass.isEnabled()); + } + }); + checkWeb.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + final boolean enabled = checkWeb.isSelected(); + textWebBind.setEnabled(enabled); + checkWebPass.setEnabled(enabled); + for (final ActionListener action : checkWebPass.getActionListeners()) { + action.actionPerformed(null); + } + } + }); + for (final ActionListener action : checkWeb.getActionListeners()) { + action.actionPerformed(null); + } + + panelOptions.add(checkAds); + panelOptions.add(checkConf); + panelInternal.add(checkMonitor); + panelInternal.add(panelShutdown); + panelWeb.add(panelWebOptions[0]); + panelWeb.add(panelWebOptions[1]); + + final GridLayout gridAction = new GridLayout(1, 2); + gridAction.setHgap(5); + final JPanel panelAction = new JPanel(gridAction); + final int pad = 6; + panelAction.setBorder(BorderFactory.createEmptyBorder(pad, pad, pad, pad)); + panelAction.add(Box.createHorizontalGlue()); + + final JButton buttonOk = new JButton("OK"); + buttonOk.setPreferredSize(new Dimension(85, 30)); + buttonOk.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + setVisible(false); + prefs.ads = checkAds.isSelected(); + prefs.confirmations = checkConf.isSelected(); + prefs.monitoring = checkMonitor.isSelected(); + prefs.shutdown = checkShutdown.isSelected(); + prefs.shutdownTime = modelShutdown.getNumber().intValue(); + prefs.web = checkWeb.isSelected(); + prefs.webBind = textWebBind.getText(); + final String webUser = textLoginUser.getText(), webPass = new String(textWebPass.getPassword()); + if (!webUser.equals(prefs.user) || !webPass.equals(DEFAULTPASSWORD)) { + prefs.webPass = StringUtil.sha1sum(webPass); + } + prefs.user = webUser; + prefs.webPassRequire = checkWebPass.isSelected() && checkWebPass.isEnabled(); + prefs.commit(); + final String loginPass = new String(textLoginPass.getPassword()); + if (!loginPass.equals(DEFAULTPASSWORD)) { + if (!DRM.login(prefs.user, loginPass)) { + prefs.user = ""; + } + } + prefs.save(); + textLoginPass.setText(DEFAULTPASSWORD); + textWebPass.setText(DEFAULTPASSWORD); + dispose(); + } + }); + final JButton buttonCancel = new JButton("Cancel"); + buttonCancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + setVisible(false); + checkAds.setSelected(prefs.ads); + checkConf.setSelected(prefs.confirmations); + checkMonitor.setSelected(prefs.monitoring); + checkShutdown.setSelected(prefs.shutdown); + modelShutdown.setValue(prefs.shutdownTime); + dispose(); + } + }); + + panelAction.add(buttonOk); + panelAction.add(buttonCancel); + + final JPanel panel = new JPanel(new GridLayout(0, 1)); + panel.setBorder(panelAction.getBorder()); + panel.add(panelLogin); + panel.add(panelOptions); + panel.add(panelInternal); + + if (!Configuration.RUNNING_FROM_JAR) { + panel.add(panelWeb); // hide web options from non-development builds for now + } + + add(panel); + add(panelAction, BorderLayout.SOUTH); + + getRootPane().setDefaultButton(buttonOk); + buttonOk.requestFocus(); + + pack(); + setLocationRelativeTo(getOwner()); + setResizable(false); + + addWindowListener(new WindowListener() { + public void windowClosing(WindowEvent arg0) { + buttonCancel.doClick(); + } + + public void windowActivated(WindowEvent arg0) { + } + + public void windowClosed(WindowEvent arg0) { + } + + public void windowDeactivated(WindowEvent arg0) { + } + + public void windowDeiconified(WindowEvent arg0) { + } + + public void windowIconified(WindowEvent arg0) { + } + + public void windowOpened(WindowEvent arg0) { + } + }); + } + + public void display() { + setVisible(true); + } +} diff --git a/src/org/rsbot/gui/SplashAd.java b/src/org/rsbot/gui/SplashAd.java new file mode 100644 index 0000000..9aecab0 --- /dev/null +++ b/src/org/rsbot/gui/SplashAd.java @@ -0,0 +1,153 @@ +package org.rsbot.gui; + +import org.rsbot.Configuration; +import org.rsbot.service.Monitoring; +import org.rsbot.service.Monitoring.Type; +import org.rsbot.util.io.HttpClient; +import org.rsbot.util.io.IniParser; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Logger; + +/** + * @author Paris + */ +public class SplashAd extends JDialog implements MouseListener { + private static Logger log = Logger.getLogger(SplashAd.class.getName()); + + private static final long serialVersionUID = 1L; + + private static final String CACHED_IMAGE = "advert.png"; + private String link; + private URL image; + private String text; + private int display = 5000; + + public SplashAd(final JFrame owner) { + super(owner); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + setUndecorated(true); + setTitle("Advertisement"); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + if (!sync()) { + dispose(); + return; + } + + final File file = new File(Configuration.Paths.getCacheDirectory(), CACHED_IMAGE); + try { + HttpClient.download(image, file); + } catch (final IOException ignored) { + dispose(); + return; + } + + if (text != null && text.length() != 0) { + log.info(text); + } + + try { + final BufferedImage img = ImageIO.read(file); + setSize(img.getWidth(), img.getHeight()); + final JLabel label = new JLabel(); + label.setIcon(new ImageIcon(img)); + add(label); + } catch (final IOException ignored) { + dispose(); + return; + } + + addMouseListener(this); + } + + private boolean sync() { + HashMap keys = null; + + try { + final URL source = new URL(Configuration.Paths.URLs.AD_INFO); + final File cache = new File(Configuration.Paths.getCacheDirectory(), "ads.txt"); + HttpClient.download(source, cache); + keys = IniParser.deserialise(cache).get(IniParser.emptySection); + } catch (final IOException e) { + return false; + } + + if (keys == null || keys.isEmpty() || !keys.containsKey("enabled") || !IniParser.parseBool(keys.get("enabled"))) { + return false; + } + if (!keys.containsKey("link")) { + return false; + } else { + link = keys.get("link"); + } + if (!keys.containsKey("image")) { + return false; + } else { + try { + image = new URL(keys.get("image")); + } catch (final MalformedURLException e) { + return false; + } + } + if (keys.containsKey("text")) { + text = keys.get("text"); + } + if (keys.containsKey("display")) { + display = Integer.parseInt(keys.get("display")); + } + + return true; + } + + public void display() { + setLocationRelativeTo(getOwner()); + setVisible(true); + + final Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + Monitoring.pushState(Type.ENVIRONMENT, "ADS", "CLICK", "false"); + dispose(); + } + }, display); + } + + @Override + public void mouseClicked(final MouseEvent e) { + } + + @Override + public void mousePressed(final MouseEvent e) { + } + + @Override + public void mouseReleased(final MouseEvent e) { + BotGUI.openURL(link); + Monitoring.pushState(Type.ENVIRONMENT, "ADS", "CLICK", "true"); + dispose(); + } + + @Override + public void mouseEntered(final MouseEvent e) { + } + + @Override + public void mouseExited(final MouseEvent e) { + } + +} diff --git a/src/org/rsbot/gui/component/JComboCheckBox.java b/src/org/rsbot/gui/component/JComboCheckBox.java new file mode 100644 index 0000000..f855e96 --- /dev/null +++ b/src/org/rsbot/gui/component/JComboCheckBox.java @@ -0,0 +1,100 @@ +package org.rsbot.gui.component; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +/** + * @author Paris + */ +public class JComboCheckBox extends JComboBox implements ActionListener { + private static final long serialVersionUID = -3388586151789454096L; + private ComboCheckRenderer renderer; + + public JComboCheckBox() { + super.addActionListener(this); + setRenderer(renderer = new ComboCheckRenderer()); + } + + @Override + public void addActionListener(final ActionListener l) { + super.removeActionListener(this); + super.addActionListener(l); + super.addActionListener(this); + } + + public void actionPerformed(final ActionEvent e) { + if (e.getModifiers() == 0) { + return; + } + final JComboBox cb = (JComboBox) e.getSource(); + final StatefulItem store = (StatefulItem) cb.getSelectedItem(); + final ComboCheckRenderer ccr = (ComboCheckRenderer) cb.getRenderer(); + ccr.checkBox.setSelected(store.state = !store.state); + } + + public void setText(final String label) { + renderer.setText(label); + } + + public String[] getSelectedItems() { + final ArrayList items = new ArrayList(); + for (int i = 0; i < getItemCount(); i++) { + final StatefulItem item = (StatefulItem) getItemAt(i); + if (item.state) { + items.add(item.id); + } + } + final String[] list = new String[items.size()]; + items.toArray(list); + return list; + } + + public void populate(final Iterable list, final boolean state) { + removeAllItems(); + for (final String item : list) { + addItem(new StatefulItem(item, state)); + } + } + + class ComboCheckRenderer implements ListCellRenderer { + final JLabel none; + final JCheckBox checkBox; + + public ComboCheckRenderer() { + none = new JLabel(); + checkBox = new JCheckBox(); + } + + public void setText(final String label) { + none.setText(label); + } + + public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { + if (index == -1) { + return none; + } + StatefulItem store = (StatefulItem) value; + checkBox.setText(store.id); + checkBox.setSelected(store.state); + return checkBox; + } + } + + class StatefulItem { + public String id; + public boolean state; + + public StatefulItem(final String id, final boolean state) { + this.id = id; + this.state = state; + } + } +} diff --git a/src/org/rsbot/gui/component/LogTextArea.java b/src/org/rsbot/gui/component/LogTextArea.java new file mode 100644 index 0000000..e6aa14c --- /dev/null +++ b/src/org/rsbot/gui/component/LogTextArea.java @@ -0,0 +1,245 @@ +package org.rsbot.gui.component; + +import org.rsbot.log.LogFormatter; +import org.rsbot.util.StringUtil; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * Non swing methods are thread safe. + */ +public class LogTextArea extends JList { + + public static final int MAX_ENTRIES = 100; + + public static final Rectangle BOTTOM_OF_WINDOW = new Rectangle(0, + Integer.MAX_VALUE, 0, 0); + + private static final long serialVersionUID = 0; + + private final LogQueue logQueue = new LogQueue(); + + private final LogAreaListModel model = new LogAreaListModel(); + + private final Runnable scrollToBottom = new Runnable() { + @Override + public void run() { + scrollRectToVisible(LogTextArea.BOTTOM_OF_WINDOW); + } + }; + + private static final Formatter formatter = new Formatter() { + private final SimpleDateFormat dateFormat = new SimpleDateFormat( + "hh:mm:ss"); + + @Override + public String format(final LogRecord record) { + final String[] className = record.getLoggerName().split("\\."); + final String name = className[className.length - 1]; + final int maxLen = 16; + final String append = "..."; + + return String.format( + "[%s] %-" + maxLen + "s %s %s", + dateFormat.format(record.getMillis()), + name.length() > maxLen ? name.substring(0, + maxLen - append.length()) + + append : name, record.getMessage(), + StringUtil.throwableToString(record.getThrown())); + } + }; + + private static final Formatter copyPasteFormatter = new LogFormatter(false); + + public LogTextArea() { + setModel(model); + setCellRenderer(new Renderer()); + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + String fontName = Font.MONOSPACED; + for (final Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) { + final String name = font.getName(); + if (name.matches("Monaco|Consolas")) { + fontName = name; + break; + } + } + setFont(new Font(fontName, Font.PLAIN, 12)); + + new Thread(logQueue, "LogGuiQueue").start(); + } + + /** + * Logs a new entry to be shown in the list. Thread safe. + * + * @param logRecord The entry. + */ + public void log(final LogRecord logRecord) { + logQueue.queue(new WrappedLogRecord(logRecord)); + } + + private class LogAreaListModel extends AbstractListModel { + private static final long serialVersionUID = 0; + + private List records = new ArrayList( + LogTextArea.MAX_ENTRIES); + + public void addAllElements(final List obj) { + records.addAll(obj); + if (getSize() > LogTextArea.MAX_ENTRIES) { + records.subList(0, (getSize() - LogTextArea.MAX_ENTRIES)).clear(); + + fireContentsChanged(this, 0, (getSize() - 1)); + } else { + fireIntervalAdded(this, (getSize() - 1), (getSize() - 1)); + } + } + + @Override + public Object getElementAt(final int index) { + return records.get(index); + } + + @Override + public int getSize() { + return records.size(); + } + + } + + /** + * Flushes every #FLUSH_RATE (miliseconds) + */ + private class LogQueue implements Runnable { + + public static final int FLUSH_RATE = 1000; + + private final Object lock = new Object(); + + private List queue = new ArrayList( + 100); + + public void queue(final WrappedLogRecord record) { + synchronized (lock) { + queue.add(record); + } + } + + @Override + public void run() { + while (true) { + List toFlush = null; + + synchronized (lock) { + if (queue.size() != 0) { + toFlush = new ArrayList(queue); + queue = queue.subList(0, 0); + } + } + if (toFlush != null) { // Hold the lock for as little time as + // possible + model.addAllElements(toFlush); + SwingUtilities.invokeLater(scrollToBottom); + } + try { + Thread.sleep(LogQueue.FLUSH_RATE); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + } + + private static class Renderer implements ListCellRenderer { + + private final Border EMPTY_BORDER = new EmptyBorder(1, 1, 1, 1); + private final Border SELECTED_BORDER = UIManager + .getBorder("List.focusCellHighlightBorder"); + private final Color DARK_GREEN = new Color(0, 90, 0); + + @Override + public Component getListCellRendererComponent(final JList list, + final Object value, final int index, final boolean isSelected, + final boolean cellHasFocus) { + if (!(value instanceof WrappedLogRecord)) { + return new JLabel(); + } + final WrappedLogRecord wlr = (WrappedLogRecord) value; + + final JTextPane result = new JTextPane(); + result.setDragEnabled(true); + result.setText(wlr.formatted); + result.setComponentOrientation(list.getComponentOrientation()); + result.setFont(list.getFont()); + result.setBorder(cellHasFocus || isSelected ? SELECTED_BORDER + : EMPTY_BORDER); + + result.setForeground(Color.DARK_GRAY); + result.setBackground(Color.WHITE); + + if (wlr.record.getLevel() == Level.SEVERE) { + result.setBackground(Color.RED); + result.setForeground(Color.WHITE); + } + + if (wlr.record.getLevel() == Level.WARNING) { + result.setForeground(Color.RED); + } + + if (wlr.record.getLevel() == Level.FINE + || wlr.record.getLevel() == Level.FINER + || wlr.record.getLevel() == Level.FINEST) { + result.setForeground(DARK_GREEN); + } + + final Object[] parameters = wlr.record.getParameters(); + if (parameters != null) { + for (final Object parameter : parameters) { + if (parameter == null) { + continue; + } + + if (parameter instanceof Color) { + result.setForeground((Color) parameter); + } else if (parameter instanceof Font) { + result.setFont((Font) parameter); + } + } + } + + return result; + } + + } + + /** + * Wrap the log records so we can control the copy paste text (via + * #toString) + */ + private class WrappedLogRecord { + + public final LogRecord record; + public final String formatted; + + public WrappedLogRecord(final LogRecord record) { + this.record = record; + formatted = LogTextArea.formatter.format(record); + } + + @Override + public String toString() { + return LogTextArea.copyPasteFormatter.format(record); + } + + } + +} \ No newline at end of file diff --git a/src/org/rsbot/gui/component/Messages.java b/src/org/rsbot/gui/component/Messages.java new file mode 100644 index 0000000..56bf7fa --- /dev/null +++ b/src/org/rsbot/gui/component/Messages.java @@ -0,0 +1,54 @@ +package org.rsbot.gui.component; + +/** + * @author Paris + */ +public interface Messages { + public static final String FILE = "File"; + public static final String EDIT = "Edit"; + public static final String VIEW = "View"; + public static final String TOOLS = "Tools"; + public static final String HELP = "Help"; + + public static final String NEWBOT = "New Bot"; + public static final String CLOSEBOT = "Close Bot"; + public static final String HIDEBOT = "Hide"; + public static final String ADDSCRIPT = "Add Script"; + public static final String RUNSCRIPT = "Run Script"; + public static final String RESUMESCRIPT = "Resume Script"; + public static final String STOPSCRIPT = "Stop Script"; + public static final String PAUSESCRIPT = "Pause Script"; + public static final String SAVESCREENSHOT = "Screenshot"; + public static final String EXIT = "Exit"; + + public static final String ACCOUNTS = "Accounts"; + public static final String FORCEINPUT = "Force Input"; + public static final String DISABLEANTIRANDOMS = "Disable Anti-Randoms"; + public static final String DISABLEAUTOLOGIN = "Disable Auto Login"; + public static final String DISABLEADS = "Disable advertisements"; + public static final String DISABLEMONITORING = "Disable monitoring"; + public static final String DISABLECONFIRMATIONS = "Disable confirmations"; + public static final String BINDTO = "Bind to address:"; + public static final String USEPASSWORD = "Use password:"; + public static final String LESSCPU = "Less CPU"; + public static final String EXTDVIEWS = "Extended views"; + public static final String AUTOSHUTDOWN = "Shutdown (mins):"; + + public static final String HIDETOOLBAR = "Hide Toolbar"; + public static final String HIDELOGPANE = "Hide Log Pane"; + public static final String ALLDEBUGGING = "All Debugging"; + + public static final String CLEARCACHE = "Clear Cache"; + public static final String OPTIONS = "Options"; + + public static final String SITE = "Site"; + public static final String PROJECT = "Project"; + public static final String ABOUT = "About"; + + public static final String TOGGLE = "Toggle"; + public static final String TOGGLEFALSE = TOGGLE + "F "; + public static final String TOGGLETRUE = TOGGLE + "T "; + public static final String MENUSEPERATOR = "-"; + + public static final String TABDEFAULTTEXT = "Game"; +} diff --git a/src/org/rsbot/loader/ClientLoader.java b/src/org/rsbot/loader/ClientLoader.java new file mode 100644 index 0000000..f9d18de --- /dev/null +++ b/src/org/rsbot/loader/ClientLoader.java @@ -0,0 +1,194 @@ +package org.rsbot.loader; + +import org.rsbot.Configuration; +import org.rsbot.loader.asm.ClassReader; +import org.rsbot.loader.script.ModScript; +import org.rsbot.loader.script.ParseException; +import org.rsbot.util.io.HttpClient; + +import javax.swing.*; +import java.io.*; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.logging.Logger; + +/** + * @author Jacmob + */ +public class ClientLoader { + + private final Logger log = Logger.getLogger(ClientLoader.class.getName()); + + private ModScript script; + private Map classes; + private int world = nextWorld(); + + public void init(final URL script, final File cache) throws IOException, ParseException { + byte[] data = null; + FileInputStream fis = null; + + try { + HttpClient.download(script, cache); + fis = new FileInputStream(cache); + data = load(fis); + } catch (final IOException ioe) { + log.severe("Could not load ModScript data"); + } finally { + try { + if (fis != null) { + fis.close(); + } + } catch (final IOException ioe1) { + } + } + + this.script = new ModScript(data); + } + + public void load(final File cache, final File version_file) throws IOException { + classes = new HashMap(); + final int version = script.getVersion(); + final String target = script.getAttribute("target"); + + int cached_version = 0; + if (cache.exists() && version_file.exists()) { + final BufferedReader reader = new BufferedReader(new FileReader(version_file)); + cached_version = Integer.parseInt(reader.readLine()); + reader.close(); + } + + if (version <= cached_version) { + final JarFile jar = new JarFile(cache); + + checkVersion(jar.getInputStream(jar.getJarEntry("client.class"))); + + log.info("Processing client"); + final Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + name = name.substring(0, name.length() - 6).replace('/', '.'); + classes.put(name, script.process(name, jar.getInputStream(entry))); + } + } + } else { + log.info("Downloading client: " + target); + final JarFile loader = getJar(target, true); + final JarFile client = getJar(target, false); + + final List replace = Arrays.asList(script.getAttribute("replace").split(" ")); + + Enumeration entries = client.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + name = name.substring(0, name.length() - 6).replace('/', '.'); + classes.put(name, load(client.getInputStream(entry))); + } + } + + entries = loader.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + name = name.substring(0, name.length() - 6).replace('/', '.'); + if (replace.contains(name)) { + classes.put(name, load(loader.getInputStream(entry))); + } + } + } + + final FileOutputStream stream = new FileOutputStream(cache); + final JarOutputStream out = new JarOutputStream(stream); + + for (final Map.Entry entry : classes.entrySet()) { + out.putNextEntry(new JarEntry(entry.getKey() + ".class")); + out.write(entry.getValue()); + } + + out.close(); + stream.close(); + + int client_version = 0; + + try { + client_version = checkVersion(new ByteArrayInputStream(classes.get("client"))); + } finally { + if (client_version != 0) { + final FileWriter writer = new FileWriter(Configuration.Paths.getVersionCache()); + writer.write(Integer.toString(client_version)); + writer.close(); + } + } + + log.info("Processing client"); + for (final Map.Entry entry : classes.entrySet()) { + entry.setValue(script.process(entry.getKey(), entry.getValue())); + } + + } + } + + public Map getClasses() { + return classes; + } + + public String getTargetName() { + return script.getAttribute("target"); + } + + private int checkVersion(final InputStream in) throws IOException { + final ClassReader reader = new ClassReader(in); + final VersionVisitor vv = new VersionVisitor(); + reader.accept(vv, ClassReader.SKIP_FRAMES); + if (vv.getVersion() != script.getVersion()) { + JOptionPane.showMessageDialog( + null, + "The bot is currently oudated, please wait patiently for a new version.", + "Outdated", + JOptionPane.INFORMATION_MESSAGE); + throw new IOException("ModScript #" + script.getVersion() + " != #" + vv.getVersion()); + } + return vv.getVersion(); + } + + private JarFile getJar(final String target, final boolean loader) { + while (true) { + try { + String s = "jar:http://world" + world + "." + target + ".com/"; + if (loader) { + s += "loader.jar!/"; + } else { + s += target + ".jar!/"; + } + final URL url = new URL(s); + final JarURLConnection juc = (JarURLConnection) url.openConnection(); + juc.setConnectTimeout(5000); + return juc.getJarFile(); + } catch (final Exception ignored) { + world = nextWorld(); + } + } + } + + private int nextWorld() { + return 1 + new Random().nextInt(169); + } + + private byte[] load(final InputStream is) throws IOException { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + final byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) != -1) { + os.write(buffer, 0, n); + } + return os.toByteArray(); + } +} diff --git a/src/org/rsbot/loader/VersionVisitor.java b/src/org/rsbot/loader/VersionVisitor.java new file mode 100644 index 0000000..93b3c77 --- /dev/null +++ b/src/org/rsbot/loader/VersionVisitor.java @@ -0,0 +1,188 @@ +package org.rsbot.loader; + +import org.rsbot.loader.asm.*; + +/** + * @author Jacmob + */ +public class VersionVisitor implements ClassVisitor { + + private int version; + + public int getVersion() { + return version; + } + + @Override + public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { + + } + + @Override + public void visitSource(final String source, final String debug) { + + } + + @Override + public void visitOuterClass(final String owner, final String name, final String desc) { + + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + return null; + } + + @Override + public void visitAttribute(final Attribute attr) { + + } + + @Override + public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) { + + } + + @Override + public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { + return null; + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { + if (!name.equals("main")) { + return null; + } + return new MethodVisitor() { + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return null; + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + return null; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { + + return null; + } + + @Override + public void visitAttribute(final Attribute attr) { + + } + + @Override + public void visitCode() { + + } + + @Override + public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { + + } + + @Override + public void visitInsn(final int opcode) { + + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + if (opcode == Opcodes.SIPUSH && operand > 400 && operand < 768) { + version = operand; + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + + } + + @Override + public void visitLabel(final Label label) { + + } + + @Override + public void visitLdcInsn(final Object cst) { + + } + + @Override + public void visitIincInsn(final int var, final int increment) { + + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) { + + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { + + } + + @Override + public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) { + + } + + @Override + public void visitLineNumber(final int line, final Label start) { + + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + + } + + @Override + public void visitEnd() { + + } + }; + } + + @Override + public void visitEnd() { + + } + +} \ No newline at end of file diff --git a/src/org/rsbot/loader/asm/AnnotationVisitor.java b/src/org/rsbot/loader/asm/AnnotationVisitor.java new file mode 100644 index 0000000..192f80c --- /dev/null +++ b/src/org/rsbot/loader/asm/AnnotationVisitor.java @@ -0,0 +1,73 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A visitor to visit a Java annotation. The methods of this interface must be + * called in the following order: (visit | visitEnum | + * visitAnnotation | visitArray)* visitEnd. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface AnnotationVisitor { + + /** + * Visits a primitive value of the annotation. + * + * @param name the value name. + * @param value the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer}, {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type}. This value can also be an array + * of byte, boolean, short, char, int, long, float or double values + * (this is equivalent to using {@link #visitArray visitArray} and + * visiting each array element in turn, but is more convenient). + */ + void visit(String name, Object value); + + /** + * Visits an enumeration value of the annotation. + * + * @param name the value name. + * @param desc the class descriptor of the enumeration class. + * @param value the actual enumeration value. + */ + void visitEnum(String name, String desc, String value); + + /** + * Visits a nested annotation value of the annotation. + * + * @param name the value name. + * @param desc the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or + * null if this visitor is not interested in visiting + * this nested annotation. The nested annotation value must be + * fully visited before calling other methods on this annotation + * visitor. + */ + AnnotationVisitor visitAnnotation(String name, String desc); + + /** + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link ClassReader} does. + * + * @param name the value name. + * @return a visitor to visit the actual array value elements, or + * null if this visitor is not interested in visiting + * these values. The 'name' parameters passed to the methods of this + * visitor are ignored. All the array values must be visited + * before calling other methods on this annotation visitor. + */ + AnnotationVisitor visitArray(String name); + + /** + * Visits the end of the annotation. + */ + void visitEnd(); +} diff --git a/src/org/rsbot/loader/asm/AnnotationWriter.java b/src/org/rsbot/loader/asm/AnnotationWriter.java new file mode 100644 index 0000000..4524a2e --- /dev/null +++ b/src/org/rsbot/loader/asm/AnnotationWriter.java @@ -0,0 +1,293 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter implements AnnotationVisitor { + + /** + * The class writer to which this annotation must be added. + */ + private final ClassWriter cw; + + /** + * The number of values in this annotation. + */ + private int size; + + /** + * true if values are named, false otherwise. Annotation + * writers used for annotation default and annotation arrays use unnamed + * values. + */ + private final boolean named; + + /** + * The annotation values in bytecode form. This byte vector only contains + * the values themselves, i.e. the number of values must be stored as a + * unsigned short just before these bytes. + */ + private final ByteVector bv; + + /** + * The byte vector to be used to store the number of values of this + * annotation. See {@link #bv}. + */ + private final ByteVector parent; + + /** + * Where the number of values of this annotation must be stored in + * {@link #parent}. + */ + private final int offset; + + /** + * Next annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter next; + + /** + * Previous annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter prev; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param cw the class writer to which this annotation must be added. + * @param named true if values are named, false otherwise. + * @param bv where the annotation values must be stored. + * @param parent where the number of annotation values must be stored. + * @param offset where in parent the number of annotation values must + * be stored. + */ + AnnotationWriter( + final ClassWriter cw, + final boolean named, + final ByteVector bv, + final ByteVector parent, + final int offset) { + this.cw = cw; + this.named = named; + this.bv = bv; + this.parent = parent; + this.offset = offset; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor interface + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + if (value instanceof String) { + bv.put12('s', cw.newUTF8((String) value)); + } else if (value instanceof Byte) { + bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + final int v = ((Boolean) value).booleanValue() ? 1 : 0; + bv.put12('Z', cw.newInteger(v).index); + } else if (value instanceof Character) { + bv.put12('C', cw.newInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + final byte[] v = (byte[]) value; + bv.put12('[', v.length); + for (final byte element : v) { + bv.put12('B', cw.newInteger(element).index); + } + } else if (value instanceof boolean[]) { + final boolean[] v = (boolean[]) value; + bv.put12('[', v.length); + for (final boolean element : v) { + bv.put12('Z', cw.newInteger(element ? 1 : 0).index); + } + } else if (value instanceof short[]) { + final short[] v = (short[]) value; + bv.put12('[', v.length); + for (final short element : v) { + bv.put12('S', cw.newInteger(element).index); + } + } else if (value instanceof char[]) { + final char[] v = (char[]) value; + bv.put12('[', v.length); + for (final char element : v) { + bv.put12('C', cw.newInteger(element).index); + } + } else if (value instanceof int[]) { + final int[] v = (int[]) value; + bv.put12('[', v.length); + for (final int element : v) { + bv.put12('I', cw.newInteger(element).index); + } + } else if (value instanceof long[]) { + final long[] v = (long[]) value; + bv.put12('[', v.length); + for (final long element : v) { + bv.put12('J', cw.newLong(element).index); + } + } else if (value instanceof float[]) { + final float[] v = (float[]) value; + bv.put12('[', v.length); + for (final float element : v) { + bv.put12('F', cw.newFloat(element).index); + } + } else if (value instanceof double[]) { + final double[] v = (double[]) value; + bv.put12('[', v.length); + for (final double element : v) { + bv.put12('D', cw.newDouble(element).index); + } + } else { + final Item i = cw.newConstItem(value); + bv.put12(".s.IFJDCS".charAt(i.type), i.index); + } + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String name, + final String desc) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag and type, and reserve space for values count + bv.put12('@', cw.newUTF8(desc)).putShort(0); + return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag, and reserve space for array size + bv.put12('[', 0); + return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + } + + @Override + public void visitEnd() { + if (parent != null) { + final byte[] data = parent.data; + data[offset] = (byte) (size >>> 8); + data[offset + 1] = (byte) size; + } + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this annotation writer list. + * + * @return the size of this annotation writer list. + */ + int getSize() { + int size = 0; + AnnotationWriter aw = this; + while (aw != null) { + size += aw.bv.length; + aw = aw.next; + } + return size; + } + + /** + * Puts the annotations of this annotation writer list into the given byte + * vector. + * + * @param out where the annotations must be put. + */ + void put(final ByteVector out) { + int n = 0; + int size = 2; + AnnotationWriter aw = this; + AnnotationWriter last = null; + while (aw != null) { + ++n; + size += aw.bv.length; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putInt(size); + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + + /** + * Puts the given annotation lists into the given byte vector. + * + * @param panns an array of annotation writer lists. + * @param off index of the first annotation to be written. + * @param out where the annotations must be put. + */ + static void put( + final AnnotationWriter[] panns, + final int off, + final ByteVector out) { + int size = 1 + 2 * (panns.length - off); + for (int i = off; i < panns.length; ++i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + out.putInt(size).putByte(panns.length - off); + for (int i = off; i < panns.length; ++i) { + AnnotationWriter aw = panns[i]; + AnnotationWriter last = null; + int n = 0; + while (aw != null) { + ++n; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + } +} diff --git a/src/org/rsbot/loader/asm/Attribute.java b/src/org/rsbot/loader/asm/Attribute.java new file mode 100644 index 0000000..6b7cc5d --- /dev/null +++ b/src/org/rsbot/loader/asm/Attribute.java @@ -0,0 +1,226 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A non standard class, field, method or code attribute. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** + * The type of this attribute. + */ + public final String type; + + /** + * The raw value of this attribute, used only for unknown attributes. + */ + byte[] value; + + /** + * The next attribute in this attribute list. May be null. + */ + Attribute next; + + /** + * Constructs a new empty attribute. + * + * @param type the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns true if this type of attribute is unknown. The default + * implementation of this method always returns true. + * + * @return true if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns true if this type of attribute is a code attribute. + * + * @return true if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or null if + * this attribute is not a code attribute that contains labels. + */ + protected Label[] getLabels() { + return null; + } + + /** + * Reads a {@link #type type} attribute. This method must return a new + * {@link Attribute} object, of type {@link #type type}, corresponding to + * the len bytes starting at the given offset, in the given class + * reader. + * + * @param cr the class that contains the attribute to be read. + * @param off index of the first byte of the attribute's content in {@link + * ClassReader#b cr.b}. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into account + * here. + * @param len the length of the attribute's content. + * @param buf buffer to be used to call + * {@link ClassReader#readUTF8 readUTF8}, + * {@link ClassReader#readClass(int, char[]) readClass} or + * {@link ClassReader#readConst readConst}. + * @param codeOff index of the first byte of code's attribute content in + * {@link ClassReader#b cr.b}, or -1 if the attribute to be read is + * not a code attribute. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into account + * here. + * @param labels the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return a new {@link Attribute} object corresponding to the given + * bytes. + */ + protected Attribute read( + final ClassReader cr, + final int off, + final int len, + final char[] buf, + final int codeOff, + final Label[] labels) { + final Attribute attr = new Attribute(type); + attr.value = new byte[len]; + System.arraycopy(cr.b, off, attr.value, 0, len); + return attr; + } + + /** + * Returns the byte array form of this attribute. + * + * @param cw the class to which this attribute must be added. This parameter + * can be used to add to the constant pool of this class the items + * that corresponds to this attribute. + * @param code the bytecode of the method corresponding to this code + * attribute, or null if this attribute is not a code + * attributes. + * @param len the length of the bytecode of the method corresponding to this + * code attribute, or null if this attribute is not a code + * attribute. + * @param maxStack the maximum stack size of the method corresponding to + * this code attribute, or -1 if this attribute is not a code + * attribute. + * @param maxLocals the maximum number of local variables of the method + * corresponding to this code attribute, or -1 if this attribute is + * not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals) { + final ByteVector v = new ByteVector(); + v.data = value; + v.length = value.length; + return v; + } + + /** + * Returns the length of the attribute list that begins with this attribute. + * + * @return the length of the attribute list that begins with this attribute. + */ + final int getCount() { + int count = 0; + Attribute attr = this; + while (attr != null) { + count += 1; + attr = attr.next; + } + return count; + } + + /** + * Returns the size of all the attributes in this attribute list. + * + * @param cw the class writer to be used to convert the attributes into byte + * arrays, with the {@link #write write} method. + * @param code the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes are + * not code attributes. + * @param maxStack the maximum stack size of the method corresponding to + * these code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these attributes + * are not code attributes. + * @return the size of all the attributes in this attribute list. This size + * includes the size of the attribute headers. + */ + final int getSize( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals) { + Attribute attr = this; + int size = 0; + while (attr != null) { + cw.newUTF8(attr.type); + size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; + attr = attr.next; + } + return size; + } + + /** + * Writes all the attributes of this attribute list in the given byte + * vector. + * + * @param cw the class writer to be used to convert the attributes into byte + * arrays, with the {@link #write write} method. + * @param code the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes are + * not code attributes. + * @param maxStack the maximum stack size of the method corresponding to + * these code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these attributes + * are not code attributes. + * @param out where the attributes must be written. + */ + final void put( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals, + final ByteVector out) { + Attribute attr = this; + while (attr != null) { + final ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); + out.putShort(cw.newUTF8(attr.type)).putInt(b.length); + out.putByteArray(b.data, 0, b.length); + attr = attr.next; + } + } +} diff --git a/src/org/rsbot/loader/asm/ByteVector.java b/src/org/rsbot/loader/asm/ByteVector.java new file mode 100644 index 0000000..a10339f --- /dev/null +++ b/src/org/rsbot/loader/asm/ByteVector.java @@ -0,0 +1,268 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to + * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** + * The content of this vector. + */ + byte[] data; + + /** + * Actual number of bytes in this vector. + */ + int length; + + /** + * Constructs a new {@link ByteVector ByteVector} with a default initial + * size. + */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector ByteVector} with the given initial + * size. + * + * @param initialSize the initial size of the byte vector to be constructed. + */ + public ByteVector(final int initialSize) { + data = new byte[initialSize]; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int b) { + int length = this.length; + if (length + 1 > data.length) { + enlarge(1); + } + data[length++] = (byte) b; + this.length = length; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b1 a byte. + * @param b2 another byte. + * @return this byte vector. + */ + ByteVector put11(final int b1, final int b2) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + final byte[] data = this.data; + data[length++] = (byte) b1; + data[length++] = (byte) b2; + this.length = length; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param s a short. + * @return this byte vector. + */ + public ByteVector putShort(final int s) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + final byte[] data = this.data; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b a byte. + * @param s a short. + * @return this byte vector. + */ + ByteVector put12(final int b, final int s) { + int length = this.length; + if (length + 3 > data.length) { + enlarge(3); + } + final byte[] data = this.data; + data[length++] = (byte) b; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param i an int. + * @return this byte vector. + */ + public ByteVector putInt(final int i) { + int length = this.length; + if (length + 4 > data.length) { + enlarge(4); + } + final byte[] data = this.data; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param l a long. + * @return this byte vector. + */ + public ByteVector putLong(final long l) { + int length = this.length; + if (length + 8 > data.length) { + enlarge(8); + } + final byte[] data = this.data; + int i = (int) (l >>> 32); + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + i = (int) l; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param s a String. + * @return this byte vector. + */ + public ByteVector putUTF8(final String s) { + final int charLength = s.length(); + int len = length; + if (len + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] data = this.data; + // optimistic algorithm: instead of computing the byte length and then + // serializing the string (which requires two loops), we assume the byte + // length is equal to char length (which is the most frequent case), and + // we start serializing the string right away. During the serialization, + // if we find that this assumption is wrong, we continue with the + // general method. + data[len++] = (byte) (charLength >>> 8); + data[len++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char c = s.charAt(i); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else { + int byteLength = i; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + data[length] = (byte) (byteLength >>> 8); + data[length + 1] = (byte) byteLength; + if (length + 2 + byteLength > data.length) { + length = len; + enlarge(2 + byteLength); + data = this.data; + } + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); + } + } + break; + } + } + length = len; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b an array of bytes. May be null to put len + * null bytes into this byte vector. + * @param off index of the fist byte of b that must be copied. + * @param len number of bytes of b that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray(final byte[] b, final int off, final int len) { + if (length + len > data.length) { + enlarge(len); + } + if (b != null) { + System.arraycopy(b, off, data, length, len); + } + length += len; + return this; + } + + /** + * Enlarge this byte vector so that it can receive n more bytes. + * + * @param size number of additional bytes that this byte vector should be + * able to receive. + */ + private void enlarge(final int size) { + final int length1 = 2 * data.length; + final int length2 = length + size; + final byte[] newData = new byte[length1 > length2 ? length1 : length2]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/src/org/rsbot/loader/asm/ClassAdapter.java b/src/org/rsbot/loader/asm/ClassAdapter.java new file mode 100644 index 0000000..fd31ca2 --- /dev/null +++ b/src/org/rsbot/loader/asm/ClassAdapter.java @@ -0,0 +1,101 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + + +/** + * An empty {@link ClassVisitor} that delegates to another {@link ClassVisitor}. + * This class can be used as a super class to quickly implement usefull class + * adapter classes, just by overriding the necessary methods. + * + * @author Eric Bruneton + */ +public class ClassAdapter implements ClassVisitor { + + /** + * The {@link ClassVisitor} to which this adapter delegates calls. + */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassAdapter} object. + * + * @param cv the class visitor to which this adapter must delegate calls. + */ + public ClassAdapter(final ClassVisitor cv) { + this.cv = cv; + } + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + cv.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(final String source, final String debug) { + cv.visitSource(source, debug); + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) { + cv.visitOuterClass(owner, name, desc); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) { + return cv.visitAnnotation(desc, visible); + } + + @Override + public void visitAttribute(final Attribute attr) { + cv.visitAttribute(attr); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) { + cv.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) { + return cv.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public void visitEnd() { + cv.visitEnd(); + } +} diff --git a/src/org/rsbot/loader/asm/ClassReader.java b/src/org/rsbot/loader/asm/ClassReader.java new file mode 100644 index 0000000..f26a828 --- /dev/null +++ b/src/org/rsbot/loader/asm/ClassReader.java @@ -0,0 +1,2029 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.rsbot.loader.asm; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A Java class parser to make a {@link ClassVisitor} visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * True to enable signatures support. + */ + static final boolean SIGNATURES = true; + + /** + * True to enable annotations support. + */ + static final boolean ANNOTATIONS = true; + + /** + * True to enable stack map frames support. + */ + static final boolean FRAMES = true; + + /** + * True to enable bytecode writing support. + */ + static final boolean WRITER = true; + + /** + * True to enable JSR_W and GOTO_W support. + */ + static final boolean RESIZE = true; + + /** + * Flag to skip method code. If this class is set CODE + * attribute won't be visited. This can be used, for example, to retrieve + * annotations for methods and method parameters. + */ + public static final int SKIP_CODE = 1; + + /** + * Flag to skip the debug information in the class. If this flag is set the + * debug information of the class is not visited, i.e. the + * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be + * called. + */ + public static final int SKIP_DEBUG = 2; + + /** + * Flag to skip the stack map frames in the class. If this flag is set the + * stack map frames of the class is not visited, i.e. the + * {@link MethodVisitor#visitFrame visitFrame} method will not be called. + * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is + * used: it avoids visiting frames that will be ignored and recomputed from + * scratch in the class writer. + */ + public static final int SKIP_FRAMES = 4; + + /** + * Flag to expand the stack map frames. By default stack map frames are + * visited in their original format (i.e. "expanded" for classes whose + * version is less than V1_6, and "compressed" for the other classes). If + * this flag is set, stack map frames are always visited in expanded format + * (this option adds a decompression/recompression step in ClassReader and + * ClassWriter which degrades performances quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * The class to be parsed. The content of this array must not be + * modified. This field is intended for {@link Attribute} sub classes, and + * is normally not needed by class generators or adapters. + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. + * The one byte offset skips the constant pool item tag that indicates its + * type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + */ + public ClassReader(final byte[] b) { + this(b, 0, b.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + * @param off the start offset of the class data. + * @param len the length of the class data. + */ + public ClassReader(final byte[] b, final int off, final int len) { + this.b = b; + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + final int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + case ClassWriter.INT: + case ClassWriter.FLOAT: + case ClassWriter.NAME_TYPE: + size = 5; + break; + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + size = 9; + ++i; + break; + case ClassWriter.UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + // case ClassWriter.CLASS: + // case ClassWriter.STR: + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may + * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 + * and those flags are represented by attributes. + * + * @return the class access flags + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the internal class name + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, the + * super class is {@link Object}. + * + * @return the internal name of super class, or null for + * {@link Object} class. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + final int n = items[readUnsignedShort(header + 4)]; + return n == 0 ? null : readUTF8(n, new char[maxStringLength]); + } + + /** + * Returns the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the array of internal names for all implemented interfaces or + * null. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + int index = header + 6; + final int n = readUnsignedShort(index); + final String[] interfaces = new String[n]; + if (n > 0) { + final char[] buf = new char[maxStringLength]; + for (int i = 0; i < n; ++i) { + index += 2; + interfaces[i] = readClass(index, buf); + } + } + return interfaces; + } + + /** + * Copies the constant pool data into the given {@link ClassWriter}. Should + * be called before the {@link #accept(ClassVisitor, int)} method. + * + * @param classWriter the {@link ClassWriter} to copy constant pool into. + */ + void copyPool(final ClassWriter classWriter) { + final char[] buf = new char[maxStringLength]; + final int ll = items.length; + final Item[] items2 = new Item[ll]; + for (int i = 1; i < ll; i++) { + int index = items[i]; + final int tag = b[index - 1]; + final Item item = new Item(i); + int nameType; + switch (tag) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + nameType = items[readUnsignedShort(index + 2)]; + item.set(tag, + readClass(index, buf), + readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + break; + + case ClassWriter.INT: + item.set(readInt(index)); + break; + + case ClassWriter.FLOAT: + item.set(Float.intBitsToFloat(readInt(index))); + break; + + case ClassWriter.NAME_TYPE: + item.set(tag, + readUTF8(index, buf), + readUTF8(index + 2, buf), + null); + break; + + case ClassWriter.LONG: + item.set(readLong(index)); + ++i; + break; + + case ClassWriter.DOUBLE: + item.set(Double.longBitsToDouble(readLong(index))); + ++i; + break; + + case ClassWriter.UTF8: { + String s = strings[i]; + if (s == null) { + index = items[i]; + s = strings[i] = readUTF(index + 2, + readUnsignedShort(index), + buf); + } + item.set(tag, s, null, null); + } + break; + + // case ClassWriter.STR: + // case ClassWriter.CLASS: + default: + item.set(tag, readUTF8(index, buf), null, null); + break; + } + + final int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + } + + final int off = items[1] - 1; + classWriter.pool.putByteArray(b, off, header - off); + classWriter.items = items2; + classWriter.threshold = (int) (0.75d * ll); + classWriter.index = ll; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is an input stream from which to read the class. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream is) throws IOException { + this(readClass(is)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param name the fully qualified name of the class to be read. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String name) throws IOException { + this(ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + + ".class")); + } + + /** + * Reads the bytecode of a class. + * + * @param is an input stream from which to read the class. + * @return the bytecode read from the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is) throws IOException { + if (is == null) { + throw new IOException("Class not found"); + } + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + final int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + final byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + final int last = is.read(); + if (last < 0) { + return b; + } + final byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor the visitor that must visit this class. + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}, + * {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, final int flags) { + accept(classVisitor, new Attribute[0], flags); + } + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor the visitor that must visit this class. + * @param attrs prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to the + * type of one the prototypes will not be parsed: its byte array + * value will be passed unchanged to the ClassWriter. This may + * corrupt it if this value contains references to the constant pool, + * or has syntactic or semantic links with a class element that has + * been transformed by a class adapter between the reader and the + * writer. + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}, + * {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attrs, + final int flags) { + final byte[] b = this.b; // the bytecode array + final char[] c = new char[maxStringLength]; // buffer used to read strings + int i, j, k; // loop variables + int u, v, w; // indexes in b + Attribute attr; + + int access; + String name; + String desc; + String attrName; + String signature; + int anns = 0; + int ianns = 0; + Attribute cattrs = null; + + // visits the header + u = header; + access = readUnsignedShort(u); + name = readClass(u + 2, c); + v = items[readUnsignedShort(u + 4)]; + final String superClassName = v == 0 ? null : readUTF8(v, c); + final String[] implementedItfs = new String[readUnsignedShort(u + 6)]; + w = 0; + u += 8; + for (i = 0; i < implementedItfs.length; ++i) { + implementedItfs[i] = readClass(u, c); + u += 2; + } + + final boolean skipCode = (flags & SKIP_CODE) != 0; + final boolean skipDebug = (flags & SKIP_DEBUG) != 0; + final boolean unzip = (flags & EXPAND_FRAMES) != 0; + + // skips fields and methods + v = u; + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + // reads the class's attributes + signature = null; + String sourceFile = null; + String sourceDebug = null; + String enclosingOwner = null; + String enclosingName = null; + String enclosingDesc = null; + + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + attrName = readUTF8(v, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("SourceFile".equals(attrName)) { + sourceFile = readUTF8(v + 6, c); + } else if ("InnerClasses".equals(attrName)) { + w = v + 6; + } else if ("EnclosingMethod".equals(attrName)) { + enclosingOwner = readClass(v + 6, c); + final int item = readUnsignedShort(v + 8); + if (item != 0) { + enclosingName = readUTF8(items[item], c); + enclosingDesc = readUTF8(items[item] + 2, c); + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(v + 6, c); + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = v + 6; + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if ("SourceDebugExtension".equals(attrName)) { + final int len = readInt(v + 2); + sourceDebug = readUTF(v + 6, len, new char[len]); + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = v + 6; + } else { + attr = readAttribute(attrs, + attrName, + v + 6, + readInt(v + 2), + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + v += 6 + readInt(v + 2); + } + // calls the visit method + classVisitor.visit(readInt(4), + access, + name, + signature, + superClassName, + implementedItfs); + + // calls the visitSource method + if (!skipDebug && (sourceFile != null || sourceDebug != null)) { + classVisitor.visitSource(sourceFile, sourceDebug); + } + + // calls the visitOuterClass method + if (enclosingOwner != null) { + classVisitor.visitOuterClass(enclosingOwner, + enclosingName, + enclosingDesc); + } + + // visits the class annotations + if (ANNOTATIONS) { + for (i = 1; i >= 0; --i) { + v = i == 0 ? ianns : anns; + if (v != 0) { + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + v = readAnnotationValues(v + 2, + c, + true, + classVisitor.visitAnnotation(readUTF8(v, c), i != 0)); + } + } + } + } + + // visits the class attributes + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + classVisitor.visitAttribute(cattrs); + cattrs = attr; + } + + // calls the visitInnerClass method + if (w != 0) { + i = readUnsignedShort(w); + w += 2; + for (; i > 0; --i) { + classVisitor.visitInnerClass(readUnsignedShort(w) == 0 + ? null + : readClass(w, c), readUnsignedShort(w + 2) == 0 + ? null + : readClass(w + 2, c), readUnsignedShort(w + 4) == 0 + ? null + : readUTF8(w + 4, c), + readUnsignedShort(w + 6)); + w += 8; + } + } + + // visits the fields + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + access = readUnsignedShort(u); + name = readUTF8(u + 2, c); + desc = readUTF8(u + 4, c); + // visits the field's attributes and looks for a ConstantValue + // attribute + int fieldValueItem = 0; + signature = null; + anns = 0; + ianns = 0; + cattrs = null; + + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + attrName = readUTF8(u, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("ConstantValue".equals(attrName)) { + fieldValueItem = readUnsignedShort(u + 6); + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 6, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 6; + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 6; + } else { + attr = readAttribute(attrs, + attrName, + u + 6, + readInt(u + 2), + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + u += 6 + readInt(u + 2); + } + // visits the field + final FieldVisitor fv = classVisitor.visitField(access, + name, + desc, + signature, + fieldValueItem == 0 ? null : readConst(fieldValueItem, c)); + // visits the field annotations and attributes + if (fv != null) { + if (ANNOTATIONS) { + for (j = 1; j >= 0; --j) { + v = j == 0 ? ianns : anns; + if (v != 0) { + k = readUnsignedShort(v); + v += 2; + for (; k > 0; --k) { + v = readAnnotationValues(v + 2, + c, + true, + fv.visitAnnotation(readUTF8(v, c), j != 0)); + } + } + } + } + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + fv.visitAttribute(cattrs); + cattrs = attr; + } + fv.visitEnd(); + } + } + + // visits the methods + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + final int u0 = u + 6; + access = readUnsignedShort(u); + name = readUTF8(u + 2, c); + desc = readUTF8(u + 4, c); + signature = null; + anns = 0; + ianns = 0; + int dann = 0; + int mpanns = 0; + int impanns = 0; + cattrs = null; + v = 0; + w = 0; + + // looks for Code and Exceptions attributes + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + attrName = readUTF8(u, c); + final int attrSize = readInt(u + 2); + u += 6; + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("Code".equals(attrName)) { + if (!skipCode) { + v = u; + } + } else if ("Exceptions".equals(attrName)) { + w = u; + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u; + } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { + dann = u; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u; + } else if (ANNOTATIONS && "RuntimeVisibleParameterAnnotations".equals(attrName)) { + mpanns = u; + } else if (ANNOTATIONS && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { + impanns = u; + } else { + attr = readAttribute(attrs, + attrName, + u, + attrSize, + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + u += attrSize; + } + // reads declared exceptions + String[] exceptions; + if (w == 0) { + exceptions = null; + } else { + exceptions = new String[readUnsignedShort(w)]; + w += 2; + for (j = 0; j < exceptions.length; ++j) { + exceptions[j] = readClass(w, c); + w += 2; + } + } + + // visits the method's code, if any + final MethodVisitor mv = classVisitor.visitMethod(access, + name, + desc, + signature, + exceptions); + + if (mv != null) { + /* + * if the returned MethodVisitor is in fact a MethodWriter, it + * means there is no method adapter between the reader and the + * writer. If, in addition, the writer's constant pool was + * copied from this reader (mw.cw.cr == this), and the signature + * and exceptions of the method have not been changed, then it + * is possible to skip all visit events and just copy the + * original code of the method to the writer (the access, name + * and descriptor can have been changed, this is not important + * since they are not copied as is from the reader). + */ + if (WRITER && mv instanceof MethodWriter) { + final MethodWriter mw = (MethodWriter) mv; + if (mw.cw.cr == this) { + if (signature == mw.signature) { + boolean sameExceptions = false; + if (exceptions == null) { + sameExceptions = mw.exceptionCount == 0; + } else { + if (exceptions.length == mw.exceptionCount) { + sameExceptions = true; + for (j = exceptions.length - 1; j >= 0; --j) { + w -= 2; + if (mw.exceptions[j] != readUnsignedShort(w)) { + sameExceptions = false; + break; + } + } + } + } + if (sameExceptions) { + /* + * we do not copy directly the code into + * MethodWriter to save a byte array copy + * operation. The real copy will be done in + * ClassWriter.toByteArray(). + */ + mw.classReaderOffset = u0; + mw.classReaderLength = u - u0; + continue; + } + } + } + } + + if (ANNOTATIONS && dann != 0) { + final AnnotationVisitor dv = mv.visitAnnotationDefault(); + readAnnotationValue(dann, c, null, dv); + if (dv != null) { + dv.visitEnd(); + } + } + if (ANNOTATIONS) { + for (j = 1; j >= 0; --j) { + w = j == 0 ? ianns : anns; + if (w != 0) { + k = readUnsignedShort(w); + w += 2; + for (; k > 0; --k) { + w = readAnnotationValues(w + 2, + c, + true, + mv.visitAnnotation(readUTF8(w, c), j != 0)); + } + } + } + } + if (ANNOTATIONS && mpanns != 0) { + readParameterAnnotations(mpanns, desc, c, true, mv); + } + if (ANNOTATIONS && impanns != 0) { + readParameterAnnotations(impanns, desc, c, false, mv); + } + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + mv.visitAttribute(cattrs); + cattrs = attr; + } + } + + if (mv != null && v != 0) { + final int maxStack = readUnsignedShort(v); + final int maxLocals = readUnsignedShort(v + 2); + final int codeLength = readInt(v + 4); + v += 8; + + final int codeStart = v; + final int codeEnd = v + codeLength; + + mv.visitCode(); + + // 1st phase: finds the labels + int label; + final Label[] labels = new Label[codeLength + 2]; + readLabel(codeLength + 1, labels); + while (v < codeEnd) { + w = v - codeStart; + int opcode = b[v] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + v += 1; + break; + case ClassWriter.LABEL_INSN: + readLabel(w + readShort(v + 1), labels); + v += 3; + break; + case ClassWriter.LABELW_INSN: + readLabel(w + readInt(v + 1), labels); + v += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[v + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + v += 6; + } else { + v += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes* + v = v + 4 - (w & 3); + // reads instruction + readLabel(w + readInt(v), labels); + j = readInt(v + 8) - readInt(v + 4) + 1; + v += 12; + for (; j > 0; --j) { + readLabel(w + readInt(v), labels); + v += 4; + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes* + v = v + 4 - (w & 3); + // reads instruction + readLabel(w + readInt(v), labels); + j = readInt(v + 4); + v += 8; + for (; j > 0; --j) { + readLabel(w + readInt(v + 4), labels); + v += 8; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + v += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + v += 3; + break; + case ClassWriter.ITFDYNMETH_INSN: + v += 5; + break; + // case MANA_INSN: + default: + v += 4; + break; + } + } + // parses the try catch entries + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + final Label start = readLabel(readUnsignedShort(v), labels); + final Label end = readLabel(readUnsignedShort(v + 2), labels); + final Label handler = readLabel(readUnsignedShort(v + 4), labels); + final int type = readUnsignedShort(v + 6); + if (type == 0) { + mv.visitTryCatchBlock(start, end, handler, null); + } else { + mv.visitTryCatchBlock(start, + end, + handler, + readUTF8(items[type], c)); + } + v += 8; + } + // parses the local variable, line number tables, and code + // attributes + int varTable = 0; + int varTypeTable = 0; + int stackMap = 0; + int stackMapSize = 0; + int frameCount = 0; + int frameMode = 0; + int frameOffset = 0; + int frameLocalCount = 0; + int frameLocalDiff = 0; + int frameStackCount = 0; + Object[] frameLocal = null; + Object[] frameStack = null; + boolean zip = true; + cattrs = null; + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + attrName = readUTF8(v, c); + if ("LocalVariableTable".equals(attrName)) { + if (!skipDebug) { + varTable = v + 6; + k = readUnsignedShort(v + 6); + w = v + 8; + for (; k > 0; --k) { + label = readUnsignedShort(w); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + label += readUnsignedShort(w + 2); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + w += 10; + } + } + } else if ("LocalVariableTypeTable".equals(attrName)) { + varTypeTable = v + 6; + } else if ("LineNumberTable".equals(attrName)) { + if (!skipDebug) { + k = readUnsignedShort(v + 6); + w = v + 8; + for (; k > 0; --k) { + label = readUnsignedShort(w); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + labels[label].line = readUnsignedShort(w + 2); + w += 4; + } + } + } else if (FRAMES && "StackMapTable".equals(attrName)) { + if ((flags & SKIP_FRAMES) == 0) { + stackMap = v + 8; + stackMapSize = readInt(v + 2); + frameCount = readUnsignedShort(v + 6); + } + /* + * here we do not extract the labels corresponding to + * the attribute content. This would require a full + * parsing of the attribute, which would need to be + * repeated in the second phase (see below). Instead the + * content of the attribute is read one frame at a time + * (i.e. after a frame has been visited, the next frame + * is read), and the labels it contains are also + * extracted one frame at a time. Thanks to the ordering + * of frames, having only a "one frame lookahead" is not + * a problem, i.e. it is not possible to see an offset + * smaller than the offset of the current insn and for + * which no Label exist. + */ + /* + * This is not true for UNINITIALIZED type offsets. We + * solve this by parsing the stack map table without a + * full decoding (see below). + */ + } else if (FRAMES && "StackMap".equals(attrName)) { + if ((flags & SKIP_FRAMES) == 0) { + stackMap = v + 8; + stackMapSize = readInt(v + 2); + frameCount = readUnsignedShort(v + 6); + zip = false; + } + /* + * IMPORTANT! here we assume that the frames are + * ordered, as in the StackMapTable attribute, although + * this is not guaranteed by the attribute format. + */ + } else { + for (k = 0; k < attrs.length; ++k) { + if (attrs[k].type.equals(attrName)) { + attr = attrs[k].read(this, + v + 6, + readInt(v + 2), + c, + codeStart - 8, + labels); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + } + } + v += 6 + readInt(v + 2); + } + + // 2nd phase: visits each instruction + if (FRAMES && stackMap != 0) { + // creates the very first (implicit) frame from the method + // descriptor + frameLocal = new Object[maxLocals]; + frameStack = new Object[maxStack]; + if (unzip) { + int local = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ("".equals(name)) { + frameLocal[local++] = Opcodes.UNINITIALIZED_THIS; + } else { + frameLocal[local++] = readClass(header + 2, c); + } + } + j = 1; + loop: + while (true) { + k = j; + switch (desc.charAt(j++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + frameLocal[local++] = Opcodes.INTEGER; + break; + case 'F': + frameLocal[local++] = Opcodes.FLOAT; + break; + case 'J': + frameLocal[local++] = Opcodes.LONG; + break; + case 'D': + frameLocal[local++] = Opcodes.DOUBLE; + break; + case '[': + while (desc.charAt(j) == '[') { + ++j; + } + if (desc.charAt(j) == 'L') { + ++j; + while (desc.charAt(j) != ';') { + ++j; + } + } + frameLocal[local++] = desc.substring(k, ++j); + break; + case 'L': + while (desc.charAt(j) != ';') { + ++j; + } + frameLocal[local++] = desc.substring(k + 1, + j++); + break; + default: + break loop; + } + } + frameLocalCount = local; + } + /* + * for the first explicit frame the offset is not + * offset_delta + 1 but only offset_delta; setting the + * implicit frame offset to -1 allow the use of the + * "offset_delta + 1" rule in all cases + */ + frameOffset = -1; + /* + * Finds labels for UNINITIALIZED frame types. Instead of + * decoding each element of the stack map table, we look + * for 3 consecutive bytes that "look like" an UNINITIALIZED + * type (tag 8, offset within code bounds, NEW instruction + * at this offset). We may find false positives (i.e. not + * real UNINITIALIZED types), but this should be rare, and + * the only consequence will be the creation of an unneeded + * label. This is better than creating a label for each NEW + * instruction, and faster than fully decoding the whole + * stack map table. + */ + for (j = stackMap; j < stackMap + stackMapSize - 2; ++j) { + if (b[j] == 8) { // UNINITIALIZED FRAME TYPE + k = readUnsignedShort(j + 1); + if (k >= 0 && k < codeLength) { // potential offset + if ((b[codeStart + k] & 0xFF) == Opcodes.NEW) { // NEW at this offset + readLabel(k, labels); + } + } + } + } + } + v = codeStart; + Label l; + while (v < codeEnd) { + w = v - codeStart; + + l = labels[w]; + if (l != null) { + mv.visitLabel(l); + if (!skipDebug && l.line > 0) { + mv.visitLineNumber(l.line, l); + } + } + + while (FRAMES && frameLocal != null + && (frameOffset == w || frameOffset == -1)) { + // if there is a frame for this offset, + // makes the visitor visit it, + // and reads the next frame if there is one. + if (!zip || unzip) { + mv.visitFrame(Opcodes.F_NEW, + frameLocalCount, + frameLocal, + frameStackCount, + frameStack); + } else if (frameOffset != -1) { + mv.visitFrame(frameMode, + frameLocalDiff, + frameLocal, + frameStackCount, + frameStack); + } + + if (frameCount > 0) { + int tag, delta, n; + if (zip) { + tag = b[stackMap++] & 0xFF; + } else { + tag = MethodWriter.FULL_FRAME; + frameOffset = -1; + } + frameLocalDiff = 0; + if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) { + delta = tag; + frameMode = Opcodes.F_SAME; + frameStackCount = 0; + } else if (tag < MethodWriter.RESERVED) { + delta = tag + - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; + stackMap = readFrameType(frameStack, + 0, + stackMap, + c, + labels); + frameMode = Opcodes.F_SAME1; + frameStackCount = 1; + } else { + delta = readUnsignedShort(stackMap); + stackMap += 2; + if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + stackMap = readFrameType(frameStack, + 0, + stackMap, + c, + labels); + frameMode = Opcodes.F_SAME1; + frameStackCount = 1; + } else if (tag >= MethodWriter.CHOP_FRAME + && tag < MethodWriter.SAME_FRAME_EXTENDED) { + frameMode = Opcodes.F_CHOP; + frameLocalDiff = MethodWriter.SAME_FRAME_EXTENDED + - tag; + frameLocalCount -= frameLocalDiff; + frameStackCount = 0; + } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) { + frameMode = Opcodes.F_SAME; + frameStackCount = 0; + } else if (tag < MethodWriter.FULL_FRAME) { + j = unzip ? frameLocalCount : 0; + for (k = tag + - MethodWriter.SAME_FRAME_EXTENDED; k > 0; k--) { + stackMap = readFrameType(frameLocal, + j++, + stackMap, + c, + labels); + } + frameMode = Opcodes.F_APPEND; + frameLocalDiff = tag + - MethodWriter.SAME_FRAME_EXTENDED; + frameLocalCount += frameLocalDiff; + frameStackCount = 0; + } else { // if (tag == FULL_FRAME) { + frameMode = Opcodes.F_FULL; + n = frameLocalDiff = frameLocalCount = readUnsignedShort(stackMap); + stackMap += 2; + for (j = 0; n > 0; n--) { + stackMap = readFrameType(frameLocal, + j++, + stackMap, + c, + labels); + } + n = frameStackCount = readUnsignedShort(stackMap); + stackMap += 2; + for (j = 0; n > 0; n--) { + stackMap = readFrameType(frameStack, + j++, + stackMap, + c, + labels); + } + } + } + frameOffset += delta + 1; + readLabel(frameOffset, labels); + + --frameCount; + } else { + frameLocal = null; + } + } + + int opcode = b[v] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + mv.visitInsn(opcode); + v += 1; + break; + case ClassWriter.IMPLVAR_INSN: + if (opcode > Opcodes.ISTORE) { + opcode -= 59; // ISTORE_0 + mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), + opcode & 0x3); + } else { + opcode -= 26; // ILOAD_0 + mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), + opcode & 0x3); + } + v += 1; + break; + case ClassWriter.LABEL_INSN: + mv.visitJumpInsn(opcode, labels[w + + readShort(v + 1)]); + v += 3; + break; + case ClassWriter.LABELW_INSN: + mv.visitJumpInsn(opcode - 33, labels[w + + readInt(v + 1)]); + v += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[v + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + mv.visitIincInsn(readUnsignedShort(v + 2), + readShort(v + 4)); + v += 6; + } else { + mv.visitVarInsn(opcode, + readUnsignedShort(v + 2)); + v += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (w & 3); + // reads instruction + label = w + readInt(v); + final int min = readInt(v + 4); + final int max = readInt(v + 8); + v += 12; + final Label[] table = new Label[max - min + 1]; + for (j = 0; j < table.length; ++j) { + table[j] = labels[w + readInt(v)]; + v += 4; + } + mv.visitTableSwitchInsn(min, + max, + labels[label], + table); + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (w & 3); + // reads instruction + label = w + readInt(v); + j = readInt(v + 4); + v += 8; + final int[] keys = new int[j]; + final Label[] values = new Label[j]; + for (j = 0; j < keys.length; ++j) { + keys[j] = readInt(v); + values[j] = labels[w + readInt(v + 4)]; + v += 8; + } + mv.visitLookupSwitchInsn(labels[label], + keys, + values); + break; + case ClassWriter.VAR_INSN: + mv.visitVarInsn(opcode, b[v + 1] & 0xFF); + v += 2; + break; + case ClassWriter.SBYTE_INSN: + mv.visitIntInsn(opcode, b[v + 1]); + v += 2; + break; + case ClassWriter.SHORT_INSN: + mv.visitIntInsn(opcode, readShort(v + 1)); + v += 3; + break; + case ClassWriter.LDC_INSN: + mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c)); + v += 2; + break; + case ClassWriter.LDCW_INSN: + mv.visitLdcInsn(readConst(readUnsignedShort(v + 1), + c)); + v += 3; + break; + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.ITFDYNMETH_INSN: + int cpIndex = items[readUnsignedShort(v + 1)]; + String iowner; + // INVOKEDYNAMIC is receiverless + if (opcode == Opcodes.INVOKEDYNAMIC) { + iowner = Opcodes.INVOKEDYNAMIC_OWNER; + } else { + iowner = readClass(cpIndex, c); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + } + final String iname = readUTF8(cpIndex, c); + final String idesc = readUTF8(cpIndex + 2, c); + if (opcode < Opcodes.INVOKEVIRTUAL) { + mv.visitFieldInsn(opcode, iowner, iname, idesc); + } else { + mv.visitMethodInsn(opcode, iowner, iname, idesc); + } + if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEDYNAMIC) { + v += 5; + } else { + v += 3; + } + break; + case ClassWriter.TYPE_INSN: + mv.visitTypeInsn(opcode, readClass(v + 1, c)); + v += 3; + break; + case ClassWriter.IINC_INSN: + mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]); + v += 3; + break; + // case MANA_INSN: + default: + mv.visitMultiANewArrayInsn(readClass(v + 1, c), + b[v + 3] & 0xFF); + v += 4; + break; + } + } + l = labels[codeEnd - codeStart]; + if (l != null) { + mv.visitLabel(l); + } + // visits the local variable tables + if (!skipDebug && varTable != 0) { + int[] typeTable = null; + if (varTypeTable != 0) { + k = readUnsignedShort(varTypeTable) * 3; + w = varTypeTable + 2; + typeTable = new int[k]; + while (k > 0) { + typeTable[--k] = w + 6; // signature + typeTable[--k] = readUnsignedShort(w + 8); // index + typeTable[--k] = readUnsignedShort(w); // start + w += 10; + } + } + k = readUnsignedShort(varTable); + w = varTable + 2; + for (; k > 0; --k) { + final int start = readUnsignedShort(w); + final int length = readUnsignedShort(w + 2); + final int index = readUnsignedShort(w + 8); + String vsignature = null; + if (typeTable != null) { + for (int a = 0; a < typeTable.length; a += 3) { + if (typeTable[a] == start + && typeTable[a + 1] == index) { + vsignature = readUTF8(typeTable[a + 2], c); + break; + } + } + } + mv.visitLocalVariable(readUTF8(w + 4, c), + readUTF8(w + 6, c), + vsignature, + labels[start], + labels[start + length], + index); + w += 10; + } + } + // visits the other attributes + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + mv.visitAttribute(cattrs); + cattrs = attr; + } + // visits the max stack and max locals values + mv.visitMaxs(maxStack, maxLocals); + } + + if (mv != null) { + mv.visitEnd(); + } + } + + // visits the end of the class + classVisitor.visitEnd(); + } + + /** + * Reads parameter annotations and makes the given visitor visit them. + * + * @param v start offset in {@link #b b} of the annotations to be read. + * @param desc the method descriptor. + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int, char[]) readClass} or + * {@link #readConst readConst}. + * @param visible true if the annotations to be read are visible + * at runtime. + * @param mv the visitor that must visit the annotations. + */ + private void readParameterAnnotations( + int v, + final String desc, + final char[] buf, + final boolean visible, + final MethodVisitor mv) { + int i; + final int n = b[v++] & 0xFF; + // workaround for a bug in javac (javac compiler generates a parameter + // annotation array whose size is equal to the number of parameters in + // the Java source file, while it should generate an array whose size is + // equal to the number of parameters in the method descriptor - which + // includes the synthetic parameters added by the compiler). This work- + // around supposes that the synthetic parameters are the first ones. + final int synthetics = Type.getArgumentTypes(desc).length - n; + AnnotationVisitor av; + for (i = 0; i < synthetics; ++i) { + // virtual annotation to detect synthetic parameters in MethodWriter + av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); + if (av != null) { + av.visitEnd(); + } + } + for (; i < n + synthetics; ++i) { + int j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); + v = readAnnotationValues(v + 2, buf, true, av); + } + } + } + + /** + * Reads the values of an annotation and makes the given visitor visit them. + * + * @param v the start offset in {@link #b b} of the values to be read + * (including the unsigned short that gives the number of values). + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int, char[]) readClass} or + * {@link #readConst readConst}. + * @param named if the annotation values are named or not. + * @param av the visitor that must visit the values. + * @return the end offset of the annotation values. + */ + private int readAnnotationValues( + int v, + final char[] buf, + final boolean named, + final AnnotationVisitor av) { + int i = readUnsignedShort(v); + v += 2; + if (named) { + for (; i > 0; --i) { + v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); + } + } else { + for (; i > 0; --i) { + v = readAnnotationValue(v, buf, null, av); + } + } + if (av != null) { + av.visitEnd(); + } + return v; + } + + /** + * Reads a value of an annotation and makes the given visitor visit it. + * + * @param v the start offset in {@link #b b} of the value to be read (not + * including the value name constant pool index). + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int, char[]) readClass} or + * {@link #readConst readConst}. + * @param name the name of the value to be read. + * @param av the visitor that must visit the value. + * @return the end offset of the annotation value. + */ + private int readAnnotationValue( + int v, + final char[] buf, + final String name, + final AnnotationVisitor av) { + int i; + if (av == null) { + switch (b[v] & 0xFF) { + case 'e': // enum_const_value + return v + 5; + case '@': // annotation_value + return readAnnotationValues(v + 3, buf, true, null); + case '[': // array_value + return readAnnotationValues(v + 1, buf, false, null); + default: + return v + 3; + } + } + switch (b[v++] & 0xFF) { + case 'I': // pointer to CONSTANT_Integer + case 'J': // pointer to CONSTANT_Long + case 'F': // pointer to CONSTANT_Float + case 'D': // pointer to CONSTANT_Double + av.visit(name, readConst(readUnsignedShort(v), buf)); + v += 2; + break; + case 'B': // pointer to CONSTANT_Byte + av.visit(name, + new Byte((byte) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'Z': // pointer to CONSTANT_Boolean + av.visit(name, readInt(items[readUnsignedShort(v)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + v += 2; + break; + case 'S': // pointer to CONSTANT_Short + av.visit(name, + new Short((short) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'C': // pointer to CONSTANT_Char + av.visit(name, + new Character((char) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 's': // pointer to CONSTANT_Utf8 + av.visit(name, readUTF8(v, buf)); + v += 2; + break; + case 'e': // enum_const_value + av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); + v += 4; + break; + case 'c': // class_info + av.visit(name, Type.getType(readUTF8(v, buf))); + v += 2; + break; + case '@': // annotation_value + v = readAnnotationValues(v + 2, + buf, + true, + av.visitAnnotation(name, readUTF8(v, buf))); + break; + case '[': // array_value + final int size = readUnsignedShort(v); + v += 2; + if (size == 0) { + return readAnnotationValues(v - 2, + buf, + false, + av.visitArray(name)); + } + switch (b[v++] & 0xFF) { + case 'B': + final byte[] bv = new byte[size]; + for (i = 0; i < size; i++) { + bv[i] = (byte) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, bv); + --v; + break; + case 'Z': + final boolean[] zv = new boolean[size]; + for (i = 0; i < size; i++) { + zv[i] = readInt(items[readUnsignedShort(v)]) != 0; + v += 3; + } + av.visit(name, zv); + --v; + break; + case 'S': + final short[] sv = new short[size]; + for (i = 0; i < size; i++) { + sv[i] = (short) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, sv); + --v; + break; + case 'C': + final char[] cv = new char[size]; + for (i = 0; i < size; i++) { + cv[i] = (char) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, cv); + --v; + break; + case 'I': + final int[] iv = new int[size]; + for (i = 0; i < size; i++) { + iv[i] = readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, iv); + --v; + break; + case 'J': + final long[] lv = new long[size]; + for (i = 0; i < size; i++) { + lv[i] = readLong(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, lv); + --v; + break; + case 'F': + final float[] fv = new float[size]; + for (i = 0; i < size; i++) { + fv[i] = Float.intBitsToFloat(readInt(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, fv); + --v; + break; + case 'D': + final double[] dv = new double[size]; + for (i = 0; i < size; i++) { + dv[i] = Double.longBitsToDouble(readLong(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, dv); + --v; + break; + default: + v = readAnnotationValues(v - 3, + buf, + false, + av.visitArray(name)); + } + } + return v; + } + + private int readFrameType( + final Object[] frame, + final int index, + int v, + final char[] buf, + final Label[] labels) { + final int type = b[v++] & 0xFF; + switch (type) { + case 0: + frame[index] = Opcodes.TOP; + break; + case 1: + frame[index] = Opcodes.INTEGER; + break; + case 2: + frame[index] = Opcodes.FLOAT; + break; + case 3: + frame[index] = Opcodes.DOUBLE; + break; + case 4: + frame[index] = Opcodes.LONG; + break; + case 5: + frame[index] = Opcodes.NULL; + break; + case 6: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case 7: // Object + frame[index] = readClass(v, buf); + v += 2; + break; + default: // Uninitialized + frame[index] = readLabel(readUnsignedShort(v), labels); + v += 2; + } + return v; + } + + /** + * Returns the label corresponding to the given offset. The default + * implementation of this method creates a label for the given offset if it + * has not been already created. + * + * @param offset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a + * label already exists for offset this method must not create a new + * one. Otherwise it must store the new label in this array. + * @return a non null Label, which must be equal to labels[offset]. + */ + protected Label readLabel(final int offset, final Label[] labels) { + if (labels[offset] == null) { + labels[offset] = new Label(); + } + return labels[offset]; + } + + /** + * Reads an attribute in {@link #b b}. + * + * @param attrs prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to the + * type of one the prototypes is ignored (i.e. an empty + * {@link Attribute} instance is returned). + * @param type the type of the attribute. + * @param off index of the first byte of the attribute's content in + * {@link #b b}. The 6 attribute header bytes, containing the type + * and the length of the attribute, are not taken into account here + * (they have already been read). + * @param len the length of the attribute's content. + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int, char[]) readClass} or + * {@link #readConst readConst}. + * @param codeOff index of the first byte of code's attribute content in + * {@link #b b}, or -1 if the attribute to be read is not a code + * attribute. The 6 attribute header bytes, containing the type and + * the length of the attribute, are not taken into account here. + * @param labels the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return the attribute that has been read, or null to skip this + * attribute. + */ + private Attribute readAttribute( + final Attribute[] attrs, + final String type, + final int off, + final int len, + final char[] buf, + final int codeOff, + final Label[] labels) { + for (int i = 0; i < attrs.length; ++i) { + if (attrs[i].type.equals(type)) { + return attrs[i].read(this, off, len, buf, codeOff, labels); + } + } + return new Attribute(type).read(this, off, len, null, -1, null); + } + + // ------------------------------------------------------------------------ + // Utility methods: low level parsing + // ------------------------------------------------------------------------ + + /** + * Returns the start index of the constant pool item in {@link #b b}, plus + * one. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param item the index a constant pool item. + * @return the start index of the constant pool item in {@link #b b}, plus + * one. + */ + public int getItem(final int item) { + return items[item]; + } + + /** + * Reads a byte value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readByte(final int index) { + return b[index] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b b}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readUnsignedShort(final int index) { + final byte[] b = this.b; + return (b[index] & 0xFF) << 8 | b[index + 1] & 0xFF; + } + + /** + * Reads a signed short value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public short readShort(final int index) { + final byte[] b = this.b; + return (short) ((b[index] & 0xFF) << 8 | b[index + 1] & 0xFF); + } + + /** + * Reads a signed int value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readInt(final int index) { + final byte[] b = this.b; + return (b[index] & 0xFF) << 24 | (b[index + 1] & 0xFF) << 16 + | (b[index + 2] & 0xFF) << 8 | b[index + 3] & 0xFF; + } + + /** + * Reads a signed long value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public long readLong(final int index) { + final long l1 = readInt(index); + final long l0 = readInt(index + 4) & 0xFFFFFFFFL; + return l1 << 32 | l0; + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. This method + * is intended for {@link Attribute} sub classes, and is normally not needed + * by class generators or adapters. + * + * @param index the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + public String readUTF8(int index, final char[] buf) { + final int item = readUnsignedShort(index); + final String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index start offset of the UTF8 string to be read. + * @param utfLen length of the UTF8 string to be read. + * @param buf buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + final int endIndex = index + utfLen; + final byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) (cc << 6 | c & 0x3F); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) (cc << 6 | c & 0x3F); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + /** + * Reads a class constant pool item in {@link #b b}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param index the start index of an unsigned short value in {@link #b b}, + * whose value is the index of a class constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified class item. + */ + public String readClass(final int index, final char[] buf) { + // computes the start index of the CONSTANT_Class item in b + // and reads the CONSTANT_Utf8 item designated by + // the first two bytes of this CONSTANT_Class item + return readUTF8(items[readUnsignedShort(index)], buf); + } + + /** + * Reads a numeric or string constant pool item in {@link #b b}. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param item the index of a constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String} or {@link Type} corresponding to + * the given constant pool item. + */ + public Object readConst(final int item, final char[] buf) { + final int index = items[item]; + switch (b[index - 1]) { + case ClassWriter.INT: + return new Integer(readInt(index)); + case ClassWriter.FLOAT: + return new Float(Float.intBitsToFloat(readInt(index))); + case ClassWriter.LONG: + return new Long(readLong(index)); + case ClassWriter.DOUBLE: + return new Double(Double.longBitsToDouble(readLong(index))); + case ClassWriter.CLASS: + return Type.getObjectType(readUTF8(index, buf)); + // case ClassWriter.STR: + default: + return readUTF8(index, buf); + } + } +} diff --git a/src/org/rsbot/loader/asm/ClassVisitor.java b/src/org/rsbot/loader/asm/ClassVisitor.java new file mode 100644 index 0000000..8aad727 --- /dev/null +++ b/src/org/rsbot/loader/asm/ClassVisitor.java @@ -0,0 +1,172 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A visitor to visit a Java class. The methods of this interface must be called + * in the following order: visit [ visitSource ] [ + * visitOuterClass ] ( visitAnnotation | + * visitAttribute )* (visitInnerClass | + * visitField | visitMethod )* visitEnd. + * + * @author Eric Bruneton + */ +public interface ClassVisitor { + + /** + * Visits the header of the class. + * + * @param version the class version. + * @param access the class's access flags (see {@link Opcodes}). This + * parameter also indicates if the class is deprecated. + * @param name the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * @param signature the signature of this class. May be null if + * the class is not a generic one, and does not extend or implement + * generic classes or interfaces. + * @param superName the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, + * the super class is {@link Object}. May be null, but + * only for the {@link Object} class. + * @param interfaces the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + */ + void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces); + + /** + * Visits the source of the class. + * + * @param source the name of the source file from which the class was + * compiled. May be null. + * @param debug additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * null. + */ + void visitSource(String source, String debug); + + /** + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner internal name of the enclosing class of the class. + * @param name the name of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + * @param desc the descriptor of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + */ + void visitOuterClass(String owner, String name, String desc); + + /** + * Visits an annotation of the class. + * + * @param desc the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + AnnotationVisitor visitAnnotation(String desc, boolean visible); + + /** + * Visits a non standard attribute of the class. + * + * @param attr an attribute. + */ + void visitAttribute(Attribute attr); + + /** + * Visits information about an inner class. This inner class is not + * necessarily a member of the class being visited. + * + * @param name the internal name of an inner class (see + * {@link Type#getInternalName() getInternalName}). + * @param outerName the internal name of the class to which the inner class + * belongs (see {@link Type#getInternalName() getInternalName}). May + * be null for not member classes. + * @param innerName the (simple) name of the inner class inside its + * enclosing class. May be null for anonymous inner + * classes. + * @param access the access flags of the inner class as originally declared + * in the enclosing class. + */ + void visitInnerClass( + String name, + String outerName, + String innerName, + int access); + + /** + * Visits a field of the class. + * + * @param access the field's access flags (see {@link Opcodes}). This + * parameter also indicates if the field is synthetic and/or + * deprecated. + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type Type}). + * @param signature the field's signature. May be null if the + * field's type does not use generic types. + * @param value the field's initial value. This parameter, which may be + * null if the field does not have an initial value, must + * be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for int, + * float, long or String fields + * respectively). This parameter is only used for static fields. + * Its value is ignored for non static fields, which must be + * initialized through bytecode instructions in constructors or + * methods. + * @return a visitor to visit field annotations and attributes, or + * null if this class visitor is not interested in + * visiting these annotations and attributes. + */ + FieldVisitor visitField( + int access, + String name, + String desc, + String signature, + Object value); + + /** + * Visits a method of the class. This method must return a new + * {@link MethodVisitor} instance (or null) each time it is + * called, i.e., it should not return a previously returned visitor. + * + * @param access the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + * @param signature the method's signature. May be null if the + * method parameters, return type and exceptions do not use generic + * types. + * @param exceptions the internal names of the method's exception classes + * (see {@link Type#getInternalName() getInternalName}). May be + * null. + * @return an object to visit the byte code of the method, or null + * if this class visitor is not interested in visiting the code of + * this method. + */ + MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions); + + /** + * Visits the end of the class. This method, which is the last one to be + * called, is used to inform the visitor that all the fields and methods of + * the class have been visited. + */ + void visitEnd(); +} diff --git a/src/org/rsbot/loader/asm/ClassWriter.java b/src/org/rsbot/loader/asm/ClassWriter.java new file mode 100644 index 0000000..2f72aa2 --- /dev/null +++ b/src/org/rsbot/loader/asm/ClassWriter.java @@ -0,0 +1,1266 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A {@link ClassVisitor} that generates classes in bytecode form. More + * precisely this visitor generates a byte array conforming to the Java class + * file format. It can be used alone, to generate a Java class "from scratch", + * or with one or more {@link ClassReader ClassReader} and adapter class visitor + * to generate a modified class from one or more existing Java classes. + * + * @author Eric Bruneton + */ +@SuppressWarnings("unchecked") +public class ClassWriter implements ClassVisitor { + + /** + * Flag to automatically compute the maximum stack size and the maximum + * number of local variables of methods. If this flag is set, then the + * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the + * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} + * method will be ignored, and computed automatically from the signature and + * the bytecode of each method. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * Flag to automatically compute the stack map frames of methods from + * scratch. If this flag is set, then the calls to the + * {@link MethodVisitor#visitFrame} method are ignored, and the stack map + * frames are recomputed from the methods bytecode. The arguments of the + * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and + * recomputed from the bytecode. In other words, computeFrames implies + * computeMaxs. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * Pseudo access flag to distinguish between the synthetic attribute and + * the synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFDYNMETH_INSN = 7; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 8; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 9; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 10; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 11; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 12; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 13; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 14; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 15; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 16; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 13; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 14; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 15; + + /** + * The class reader from which this class writer was constructed, if any. + */ + ClassReader cr; + + /** + * Minor and major version numbers of the class to be generated. + */ + int version; + + /** + * Index of the next item to be added in the constant pool. + */ + int index; + + /** + * The constant pool of this class. + */ + final ByteVector pool; + + /** + * The constant pool's hash table data. + */ + Item[] items; + + /** + * The threshold of the constant pool's hash table. + */ + int threshold; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key2; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key3; + + /** + * A type table used to temporarily store internal names that will not + * necessarily be stored in the constant pool. This type table is used by + * the control flow and data flow analysis algorithm used to compute stack + * map frames from scratch. This array associates to each index i + * the Item whose index is i. All Item objects stored in this + * array are also stored in the {@link #items} hash table. These two arrays + * allow to retrieve an Item from its index or, conversely, to get the index + * of an Item from its value. Each Item stores an internal name in its + * {@link Item#strVal1} field. + */ + Item[] typeTable; + + /** + * Number of elements in the {@link #typeTable} array. + */ + private short typeCount; + + /** + * The access flags of this class. + */ + private int access; + + /** + * The constant pool item that contains the internal name of this class. + */ + private int name; + + /** + * The internal name of this class. + */ + String thisName; + + /** + * The constant pool item that contains the signature of this class. + */ + private int signature; + + /** + * The constant pool item that contains the internal name of the super class + * of this class. + */ + private int superName; + + /** + * Number of interfaces implemented or extended by this class or interface. + */ + private int interfaceCount; + + /** + * The interfaces implemented or extended by this class or interface. More + * precisely, this array contains the indexes of the constant pool items + * that contain the internal names of these interfaces. + */ + private int[] interfaces; + + /** + * The index of the constant pool item that contains the name of the source + * file from which this class was compiled. + */ + private int sourceFile; + + /** + * The SourceDebug attribute of this class. + */ + private ByteVector sourceDebug; + + /** + * The constant pool item that contains the name of the enclosing class of + * this class. + */ + private int enclosingMethodOwner; + + /** + * The constant pool item that contains the name and descriptor of the + * enclosing method of this class. + */ + private int enclosingMethod; + + /** + * The runtime visible annotations of this class. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this class. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this class. + */ + private Attribute attrs; + + /** + * The number of entries in the InnerClasses attribute. + */ + private int innerClassesCount; + + /** + * The InnerClasses attribute. + */ + private ByteVector innerClasses; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#next} field. This field stores the first element of + * this list. + */ + FieldWriter firstField; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#next} field. This field stores the last element of + * this list. + */ + FieldWriter lastField; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#next} field. This field stores the first element of + * this list. + */ + MethodWriter firstMethod; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#next} field. This field stores the last element of + * this list. + */ + MethodWriter lastMethod; + + /** + * true if the maximum stack size and number of local variables + * must be automatically computed. + */ + private final boolean computeMaxs; + + /** + * true if the stack map frames must be recomputed from scratch. + */ + private final boolean computeFrames; + + /** + * true if the stack map tables of this class are invalid. The + * {@link MethodWriter#resizeInstructions} method cannot transform existing + * stack map tables, and so produces potentially invalid classes when it is + * executed. In this case the class is reread and rewritten with the + * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize + * stack map tables when this option is used). + */ + boolean invalidFrames; + + // ------------------------------------------------------------------------ + // Static initializer + // ------------------------------------------------------------------------ + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + final byte[] b = new byte[220]; + final String s = "AAAAAAAAAAAAAAAABCKLLDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIDNOAA" + + "AAAAGGGGGGGHHFBFAAFFAAQPIIJJIIIIIIIIIIIIIIIIII"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + } + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #COMPUTE_MAXS}, {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + index = 1; + pool = new ByteVector(); + items = new Item[256]; + threshold = (int) (0.75d * items.length); + key = new Item(); + key2 = new Item(); + key3 = new Item(); + computeMaxs = (flags & COMPUTE_MAXS) != 0; + computeFrames = (flags & COMPUTE_FRAMES) != 0; + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for + * "mostly add" bytecode transformations. These optimizations are the + * following: + *

+ *

  • The constant pool from the original class is copied as is in + * the new class, which saves time. New constant pool entries will be added + * at the end if necessary, but unused constant pool entries won't be + * removed.
  • Methods that are not transformed are copied as + * is in the new class, directly from the original class bytecode (i.e. + * without emitting visit events for all the method instructions), which + * saves a lot of time. Untransformed methods are detected by the + * fact that the {@link ClassReader} receives {@link MethodVisitor} objects + * that come from a {@link ClassWriter} (and not from a custom + * {@link ClassVisitor} instance).
  • + *
+ * + * @param classReader the {@link ClassReader} used to read the original + * class. It will be used to copy the entire constant pool from the + * original class and also to copy other fragments of original + * bytecode where applicable. + * @param flags option flags that can be used to modify the default behavior + * of this class. These option flags do not affect methods that + * are copied as is in the new class. This means that the maximum + * stack size nor the stack frames will be computed for these + * methods. See {@link #COMPUTE_MAXS}, {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + this(flags); + classReader.copyPool(this); + cr = classReader; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor interface + // ------------------------------------------------------------------------ + + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + this.version = version; + this.access = access; + this.name = newClass(name); + thisName = name; + if (ClassReader.SIGNATURES && signature != null) { + this.signature = newUTF8(signature); + } + this.superName = superName == null ? 0 : newClass(superName); + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = newClass(interfaces[i]); + } + } + } + + public void visitSource(final String file, final String debug) { + if (file != null) { + sourceFile = newUTF8(file); + } + if (debug != null) { + sourceDebug = new ByteVector().putUTF8(debug); + } + } + + public void visitOuterClass( + final String owner, + final String name, + final String desc) { + enclosingMethodOwner = newClass(owner); + if (name != null && desc != null) { + enclosingMethod = newNameType(name, desc); + } + } + + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + final ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + final AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + ++innerClassesCount; + innerClasses.putShort(name == null ? 0 : newClass(name)); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + } + + public FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) { + return new FieldWriter(this, access, name, desc, signature, value); + } + + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + return new MethodWriter(this, + access, + name, + desc, + signature, + exceptions, + computeMaxs, + computeFrames); + } + + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Other public methods + // ------------------------------------------------------------------------ + + /** + * Returns the bytecode of the class that was build with this class writer. + * + * @return the bytecode of the class that was build with this class writer. + */ + public byte[] toByteArray() { + // computes the real size of the bytecode of this class + int size = 24 + 2 * interfaceCount; + int nbFields = 0; + FieldWriter fb = firstField; + while (fb != null) { + ++nbFields; + size += fb.getSize(); + fb = fb.next; + } + int nbMethods = 0; + MethodWriter mb = firstMethod; + while (mb != null) { + ++nbMethods; + size += mb.getSize(); + mb = mb.next; + } + int attributeCount = 0; + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + size += 8; + newUTF8("Signature"); + } + if (sourceFile != 0) { + ++attributeCount; + size += 8; + newUTF8("SourceFile"); + } + if (sourceDebug != null) { + ++attributeCount; + size += sourceDebug.length + 4; + newUTF8("SourceDebugExtension"); + } + if (enclosingMethodOwner != 0) { + ++attributeCount; + size += 10; + newUTF8("EnclosingMethod"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + size += 6; + newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((version & 0xFFFF) < Opcodes.V1_5 || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + ++attributeCount; + size += 6; + newUTF8("Synthetic"); + } + if (innerClasses != null) { + ++attributeCount; + size += 8 + innerClasses.length; + newUTF8("InnerClasses"); + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + size += 8 + anns.getSize(); + newUTF8("RuntimeVisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + size += 8 + ianns.getSize(); + newUTF8("RuntimeInvisibleAnnotations"); + } + if (attrs != null) { + attributeCount += attrs.getCount(); + size += attrs.getSize(this, null, 0, -1, -1); + } + size += pool.length; + // allocates a byte vector of this size, in order to avoid unnecessary + // arraycopy operations in the ByteVector.enlarge() method + final ByteVector out = new ByteVector(size); + out.putInt(0xCAFEBABE).putInt(version); + out.putShort(index).putByteArray(pool.data, 0, pool.length); + final int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(superName); + out.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + out.putShort(interfaces[i]); + } + out.putShort(nbFields); + fb = firstField; + while (fb != null) { + fb.put(out); + fb = fb.next; + } + out.putShort(nbMethods); + mb = firstMethod; + while (mb != null) { + mb.put(out); + mb = mb.next; + } + out.putShort(attributeCount); + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); + } + if (sourceFile != 0) { + out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); + } + if (sourceDebug != null) { + final int len = sourceDebug.length - 2; + out.putShort(newUTF8("SourceDebugExtension")).putInt(len); + out.putByteArray(sourceDebug.data, 2, len); + } + if (enclosingMethodOwner != 0) { + out.putShort(newUTF8("EnclosingMethod")).putInt(4); + out.putShort(enclosingMethodOwner).putShort(enclosingMethod); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(newUTF8("Deprecated")).putInt(0); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((version & 0xFFFF) < Opcodes.V1_5 || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + out.putShort(newUTF8("Synthetic")).putInt(0); + } + if (innerClasses != null) { + out.putShort(newUTF8("InnerClasses")); + out.putInt(innerClasses.length + 2).putShort(innerClassesCount); + out.putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(this, null, 0, -1, -1, out); + } + if (invalidFrames) { + final ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES); + return cw.toByteArray(); + } + return out.data; + } + + // ------------------------------------------------------------------------ + // Utility methods: constant pool management + // ------------------------------------------------------------------------ + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * + * @param cst the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double}, a {@link String} or a + * {@link Type}. + * @return a new or already existing constant item with the given value. + */ + Item newConstItem(final Object cst) { + if (cst instanceof Integer) { + final int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Byte) { + final int val = ((Byte) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Character) { + final int val = ((Character) cst).charValue(); + return newInteger(val); + } else if (cst instanceof Short) { + final int val = ((Short) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Boolean) { + final int val = ((Boolean) cst).booleanValue() ? 1 : 0; + return newInteger(val); + } else if (cst instanceof Float) { + final float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + final long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + final double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + final Type t = (Type) cst; + return newClassItem(t.getSort() == Type.OBJECT + ? t.getInternalName() + : t.getDescriptor()); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param cst the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the + * given value. + */ + public int newConst(final Object cst) { + return newConstItem(cst).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param value the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + key.set(UTF8, value, null, null); + Item result = get(key); + if (result == null) { + pool.putByte(UTF8).putUTF8(value); + result = new Item(index++, key); + put(result); + } + return result.index; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value the internal name of the class. + * @return a new or already existing class reference item. + */ + Item newClassItem(final String value) { + key2.set(CLASS, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(CLASS, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return newClassItem(value).index; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param desc the field's descriptor. + * @return a new or already existing field reference item. + */ + Item newFieldItem(final String owner, final String name, final String desc) { + key3.set(FIELD, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(FIELD, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param desc the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String desc) { + return newFieldItem(owner, name, desc).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param desc the method's descriptor. + * @param itf true if owner is an interface. + * @return a new or already existing method reference item. + */ + Item newMethodItem( + final String owner, + final String name, + final String desc, + final boolean itf) { + final int type = itf ? IMETH : METH; + key3.set(type, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(type, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param desc the method's descriptor. + * @param itf true if owner is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod( + final String owner, + final String name, + final String desc, + final boolean itf) { + return newMethodItem(owner, name, desc, itf).index; + } + + /** + * Adds an integer to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param value the int value. + * @return a new or already existing int item. + */ + Item newInteger(final int value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(INT).putInt(value); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a float to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the float value. + * @return a new or already existing float item. + */ + Item newFloat(final float value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(FLOAT).putInt(key.intVal); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a long to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the long value. + * @return a new or already existing long item. + */ + Item newLong(final long value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(LONG).putLong(value); + result = new Item(index, key); + put(result); + index += 2; + } + return result; + } + + /** + * Adds a double to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the double value. + * @return a new or already existing double item. + */ + Item newDouble(final double value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(DOUBLE).putLong(key.longVal); + result = new Item(index, key); + put(result); + index += 2; + } + return result; + } + + /** + * Adds a string to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the String value. + * @return a new or already existing string item. + */ + private Item newString(final String value) { + key2.set(STR, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(STR, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param name a name. + * @param desc a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String desc) { + return newNameTypeItem(name, desc).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param name a name. + * @param desc a type descriptor. + * @return a new or already existing name and type item. + */ + Item newNameTypeItem(final String name, final String desc) { + key2.set(NAME_TYPE, name, desc, null); + Item result = get(key2); + if (result == null) { + put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds the given internal name to {@link #typeTable} and returns its index. + * Does nothing if the type table already contains this internal name. + * + * @param type the internal name to be added to the type table. + * @return the index of this internal name in the type table. + */ + int addType(final String type) { + key.set(TYPE_NORMAL, type, null, null); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given "uninitialized" type to {@link #typeTable} and returns its + * index. This method is used for UNINITIALIZED types, made of an internal + * name and a bytecode offset. + * + * @param type the internal name to be added to the type table. + * @param offset the bytecode offset of the NEW instruction that created + * this UNINITIALIZED type value. + * @return the index of this internal name in the type table. + */ + int addUninitializedType(final String type, final int offset) { + key.type = TYPE_UNINIT; + key.intVal = offset; + key.strVal1 = type; + key.hashCode = 0x7FFFFFFF & TYPE_UNINIT + type.hashCode() + offset; + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given Item to {@link #typeTable}. + * + * @param item the value to be added to the type table. + * @return the added Item, which a new Item instance with the same value as + * the given Item. + */ + private Item addType(final Item item) { + ++typeCount; + final Item result = new Item(typeCount, key); + put(result); + if (typeTable == null) { + typeTable = new Item[16]; + } + if (typeCount == typeTable.length) { + final Item[] newTable = new Item[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); + typeTable = newTable; + } + typeTable[typeCount] = result; + return result; + } + + /** + * Returns the index of the common super type of the two given types. This + * method calls {@link #getCommonSuperClass} and caches the result in the + * {@link #items} hash table to speedup future calls with the same + * parameters. + * + * @param type1 index of an internal name in {@link #typeTable}. + * @param type2 index of an internal name in {@link #typeTable}. + * @return the index of the common super type of the two given types. + */ + int getMergedType(final int type1, final int type2) { + key2.type = TYPE_MERGED; + key2.longVal = type1 | (long) type2 << 32; + key2.hashCode = 0x7FFFFFFF & TYPE_MERGED + type1 + type2; + Item result = get(key2); + if (result == null) { + final String t = typeTable[type1].strVal1; + final String u = typeTable[type2].strVal1; + key2.intVal = addType(getCommonSuperClass(t, u)); + result = new Item((short) 0, key2); + put(result); + } + return result.intVal; + } + + /** + * Returns the common super type of the two given types. The default + * implementation of this method loads the two given classes and uses + * the java.lang.Class methods to find the common super class. It can be + * overridden to compute this common super type in other ways, in particular + * without actually loading any class, or to take into account the class + * that is currently being generated by this ClassWriter, which can of + * course not be loaded since it is under construction. + * + * @param type1 the internal name of a class. + * @param type2 the internal name of another class. + * @return the internal name of the common super class of the two given + * classes. + */ + @SuppressWarnings("rawtypes") + protected String getCommonSuperClass(final String type1, final String type2) { + Class c, d; + try { + c = Class.forName(type1.replace('/', '.')); + d = Class.forName(type2.replace('/', '.')); + } catch (final Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + + /** + * Returns the constant pool's hash table item which is equal to the given + * item. + * + * @param key a constant pool item. + * @return the constant pool's hash table item which is equal to the given + * item, or null if there is no such item. + */ + private Item get(final Item key) { + Item i = items[key.hashCode % items.length]; + while (i != null && (i.type != key.type || !key.isEqualTo(i))) { + i = i.next; + } + return i; + } + + /** + * Puts the given item in the constant pool's hash table. The hash table + * must not already contains this item. + * + * @param i the item to be added to the constant pool's hash table. + */ + private void put(final Item i) { + if (index > threshold) { + final int ll = items.length; + final int nl = ll * 2 + 1; + final Item[] newItems = new Item[nl]; + for (int l = ll - 1; l >= 0; --l) { + Item j = items[l]; + while (j != null) { + final int index = j.hashCode % newItems.length; + final Item k = j.next; + j.next = newItems[index]; + newItems[index] = j; + j = k; + } + } + items = newItems; + threshold = (int) (nl * 0.75); + } + final int index = i.hashCode % items.length; + i.next = items[index]; + items[index] = i; + } + + /** + * Puts one byte and two shorts into the constant pool. + * + * @param b a byte. + * @param s1 a short. + * @param s2 another short. + */ + private void put122(final int b, final int s1, final int s2) { + pool.put12(b, s1).putShort(s2); + } +} diff --git a/src/org/rsbot/loader/asm/Edge.java b/src/org/rsbot/loader/asm/Edge.java new file mode 100644 index 0000000..ae0cfe6 --- /dev/null +++ b/src/org/rsbot/loader/asm/Edge.java @@ -0,0 +1,51 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * An edge in the control flow graph of a method body. See {@link Label Label}. + * + * @author Eric Bruneton + */ +class Edge { + + /** + * Denotes a normal control flow graph edge. + */ + static final int NORMAL = 0; + + /** + * Denotes a control flow graph edge corresponding to an exception handler. + * More precisely any {@link Edge} whose {@link #info} is strictly positive + * corresponds to an exception handler. The actual value of {@link #info} is + * the index, in the {@link ClassWriter} type table, of the exception that + * is catched. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. If + * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) + * stack size in the basic block from which this edge originates. This size + * is equal to the stack size at the "jump" instruction to which this edge + * corresponds, relatively to the stack size at the beginning of the + * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, + * this field is the kind of this control flow graph edge (i.e. NORMAL or + * EXCEPTION). + */ + int info; + + /** + * The successor block of the basic block from which this edge originates. + */ + Label successor; + + /** + * The next edge in the list of successors of the originating basic block. + * See {@link Label#successors successors}. + */ + Edge next; +} diff --git a/src/org/rsbot/loader/asm/FieldVisitor.java b/src/org/rsbot/loader/asm/FieldVisitor.java new file mode 100644 index 0000000..056d026 --- /dev/null +++ b/src/org/rsbot/loader/asm/FieldVisitor.java @@ -0,0 +1,40 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A visitor to visit a Java field. The methods of this interface must be called + * in the following order: ( visitAnnotation | + * visitAttribute )* visitEnd. + * + * @author Eric Bruneton + */ +public interface FieldVisitor { + + /** + * Visits an annotation of the field. + * + * @param desc the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + AnnotationVisitor visitAnnotation(String desc, boolean visible); + + /** + * Visits a non standard attribute of the field. + * + * @param attr an attribute. + */ + void visitAttribute(Attribute attr); + + /** + * Visits the end of the field. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the field have been visited. + */ + void visitEnd(); +} diff --git a/src/org/rsbot/loader/asm/FieldWriter.java b/src/org/rsbot/loader/asm/FieldWriter.java new file mode 100644 index 0000000..af768f3 --- /dev/null +++ b/src/org/rsbot/loader/asm/FieldWriter.java @@ -0,0 +1,243 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * An {@link FieldVisitor} that generates Java fields in bytecode form. + * + * @author Eric Bruneton + */ +final class FieldWriter implements FieldVisitor { + + /** + * Next field writer (see {@link ClassWriter#firstField firstField}). + */ + FieldWriter next; + + /** + * The class writer to which this field must be added. + */ + private final ClassWriter cw; + + /** + * Access flags of this field. + */ + private final int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * field. + */ + private final int desc; + + /** + * The index of the constant pool item that contains the signature of this + * field. + */ + private int signature; + + /** + * The index of the constant pool item that contains the constant value of + * this field. + */ + private int value; + + /** + * The runtime visible annotations of this field. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this field. May be null. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this field. May be null. + */ + private Attribute attrs; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link FieldWriter}. + * + * @param cw the class writer to which this field must be added. + * @param access the field's access flags (see {@link Opcodes}). + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be null. + * @param value the field's constant value. May be null. + */ + FieldWriter( + final ClassWriter cw, + final int access, + final String name, + final String desc, + final String signature, + final Object value) { + if (cw.firstField == null) { + cw.firstField = this; + } else { + cw.lastField.next = this; + } + cw.lastField = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + if (ClassReader.SIGNATURES && signature != null) { + this.signature = cw.newUTF8(signature); + } + if (value != null) { + this.value = cw.newConstItem(value).index; + } + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor interface + // ------------------------------------------------------------------------ + + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + final ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + final AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this field. + * + * @return the size of this field. + */ + int getSize() { + int size = 8; + if (value != 0) { + cw.newUTF8("ConstantValue"); + size += 8; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + cw.newUTF8("Synthetic"); + size += 6; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != 0) { + cw.newUTF8("Signature"); + size += 8; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the content of this field into the given byte vector. + * + * @param out where the content of this field must be put. + */ + void put(final ByteVector out) { + final int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(desc); + int attributeCount = 0; + if (value != 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + ++attributeCount; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (value != 0) { + out.putShort(cw.newUTF8("ConstantValue")); + out.putInt(2).putShort(value); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(cw.newUTF8("Signature")); + out.putInt(2).putShort(signature); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } +} diff --git a/src/org/rsbot/loader/asm/Frame.java b/src/org/rsbot/loader/asm/Frame.java new file mode 100644 index 0000000..4046e7c --- /dev/null +++ b/src/org/rsbot/loader/asm/Frame.java @@ -0,0 +1,1136 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * Information about the input and output stack map frames of a basic block. + * + * @author Eric Bruneton + */ +final class Frame { + + /** + * Mask to get the dimension of a frame type. This dimension is a signed + * integer between -8 and 7. + */ + static final int DIM = 0xF0000000; + + /** + * Constant to be added to a type to get a type with one more dimension. + */ + static final int ARRAY_OF = 0x10000000; + + /** + * Constant to be added to a type to get a type with one less dimension. + */ + static final int ELEMENT_OF = 0xF0000000; + + /** + * Mask to get the kind of a frame type. + * + * @see #BASE + * @see #LOCAL + * @see #STACK + */ + static final int KIND = 0xF000000; + + /** + * Flag used for LOCAL and STACK types. Indicates that if this type happens + * to be a long or double type (during the computations of input frames), + * then it must be set to TOP because the second word of this value has + * been reused to store other data in the basic block. Hence the first word + * no longer stores a valid long or double value. + */ + static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + + /** + * Mask to get the value of a frame type. + */ + static final int VALUE = 0x7FFFFF; + + /** + * Mask to get the kind of base types. + */ + static final int BASE_KIND = 0xFF00000; + + /** + * Mask to get the value of base types. + */ + static final int BASE_VALUE = 0xFFFFF; + + /** + * Kind of the types that are not relative to an input stack map frame. + */ + static final int BASE = 0x1000000; + + /** + * Base kind of the base reference types. The BASE_VALUE of such types is an + * index into the type table. + */ + static final int OBJECT = BASE | 0x700000; + + /** + * Base kind of the uninitialized base types. The BASE_VALUE of such types + * in an index into the type table (the Item at that index contains both an + * instruction offset and an internal class name). + */ + static final int UNINITIALIZED = BASE | 0x800000; + + /** + * Kind of the types that are relative to the local variable types of an + * input stack map frame. The value of such types is a local variable index. + */ + private static final int LOCAL = 0x2000000; + + /** + * Kind of the the types that are relative to the stack of an input stack + * map frame. The value of such types is a position relatively to the top of + * this stack. + */ + private static final int STACK = 0x3000000; + + /** + * The TOP type. This is a BASE type. + */ + static final int TOP = BASE | 0; + + /** + * The BOOLEAN type. This is a BASE type mainly used for array types. + */ + static final int BOOLEAN = BASE | 9; + + /** + * The BYTE type. This is a BASE type mainly used for array types. + */ + static final int BYTE = BASE | 10; + + /** + * The CHAR type. This is a BASE type mainly used for array types. + */ + static final int CHAR = BASE | 11; + + /** + * The SHORT type. This is a BASE type mainly used for array types. + */ + static final int SHORT = BASE | 12; + + /** + * The INTEGER type. This is a BASE type. + */ + static final int INTEGER = BASE | 1; + + /** + * The FLOAT type. This is a BASE type. + */ + static final int FLOAT = BASE | 2; + + /** + * The DOUBLE type. This is a BASE type. + */ + static final int DOUBLE = BASE | 3; + + /** + * The LONG type. This is a BASE type. + */ + static final int LONG = BASE | 4; + + /** + * The NULL type. This is a BASE type. + */ + static final int NULL = BASE | 5; + + /** + * The UNINITIALIZED_THIS type. This is a BASE type. + */ + static final int UNINITIALIZED_THIS = BASE | 6; + + /** + * The stack size variation corresponding to each JVM instruction. This + * stack variation is equal to the size of the values produced by an + * instruction, minus the size of the values consumed by this instruction. + */ + static final int[] SIZE; + + /** + * Computes the stack size variation corresponding to each JVM instruction. + */ + static { + int i; + final int[] b = new int[202]; + final String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; + for (i = 0; i < b.length; ++i) { + b[i] = s.charAt(i) - 'E'; + } + SIZE = b; + } + + /** + * The label (i.e. basic block) to which these input and output stack map + * frames correspond. + */ + Label owner; + + /** + * The input stack map frame locals. + */ + int[] inputLocals; + + /** + * The input stack map frame stack. + */ + int[] inputStack; + + /** + * The output stack map frame locals. + */ + private int[] outputLocals; + + /** + * The output stack map frame stack. + */ + private int[] outputStack; + + /** + * Relative size of the output stack. The exact semantics of this field + * depends on the algorithm that is used. + *

+ * When only the maximum stack size is computed, this field is the size of + * the output stack relatively to the top of the input stack. + *

+ * When the stack map frames are completely computed, this field is the + * actual number of types in {@link #outputStack}. + */ + private int outputStackTop; + + /** + * Number of types that are initialized in the basic block. + * + * @see #initializations + */ + private int initializationCount; + + /** + * The types that are initialized in the basic block. A constructor + * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace + * every occurence of this type in the local variables and in the + * operand stack. This cannot be done during the first phase of the + * algorithm since, during this phase, the local variables and the operand + * stack are not completely computed. It is therefore necessary to store the + * types on which constructors are invoked in the basic block, in order to + * do this replacement during the second phase of the algorithm, where the + * frames are fully computed. Note that this array can contain types that + * are relative to input locals or to the input stack (see below for the + * description of the algorithm). + */ + private int[] initializations; + + /** + * Returns the output frame local variable type at the given index. + * + * @param local the index of the local that must be returned. + * @return the output frame local variable type at the given index. + */ + private int get(final int local) { + if (outputLocals == null || local >= outputLocals.length) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + return LOCAL | local; + } else { + int type = outputLocals[local]; + if (type == 0) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + type = outputLocals[local] = LOCAL | local; + } + return type; + } + } + + /** + * Sets the output frame local variable type at the given index. + * + * @param local the index of the local that must be set. + * @param type the value of the local that must be set. + */ + private void set(final int local, final int type) { + // creates and/or resizes the output local variables array if necessary + if (outputLocals == null) { + outputLocals = new int[10]; + } + final int n = outputLocals.length; + if (local >= n) { + final int[] t = new int[Math.max(local + 1, 2 * n)]; + System.arraycopy(outputLocals, 0, t, 0, n); + outputLocals = t; + } + // sets the local variable + outputLocals[local] = type; + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param type the type that must be pushed. + */ + private void push(final int type) { + // creates and/or resizes the output stack array if necessary + if (outputStack == null) { + outputStack = new int[10]; + } + final int n = outputStack.length; + if (outputStackTop >= n) { + final int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; + System.arraycopy(outputStack, 0, t, 0, n); + outputStack = t; + } + // pushes the type on the output stack + outputStack[outputStackTop++] = type; + // updates the maximun height reached by the output stack, if needed + final int top = owner.inputStackTop + outputStackTop; + if (top > owner.outputStackMax) { + owner.outputStackMax = top; + } + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param cw the ClassWriter to which this label belongs. + * @param desc the descriptor of the type to be pushed. Can also be a method + * descriptor (in this case this method pushes its return type onto + * the output frame stack). + */ + private void push(final ClassWriter cw, final String desc) { + final int type = type(cw, desc); + if (type != 0) { + push(type); + if (type == LONG || type == DOUBLE) { + push(TOP); + } + } + } + + /** + * Returns the int encoding of the given type. + * + * @param cw the ClassWriter to which this label belongs. + * @param desc a type descriptor. + * @return the int encoding of the given type. + */ + private static int type(final ClassWriter cw, final String desc) { + String t; + final int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + // stores the internal name, not the descriptor! + t = desc.substring(index + 1, desc.length() - 1); + return OBJECT | cw.addType(t); + // case '[': + default: + // extracts the dimensions and the element type + int data; + int dims = index + 1; + while (desc.charAt(dims) == '[') { + ++dims; + } + switch (desc.charAt(dims)) { + case 'Z': + data = BOOLEAN; + break; + case 'C': + data = CHAR; + break; + case 'B': + data = BYTE; + break; + case 'S': + data = SHORT; + break; + case 'I': + data = INTEGER; + break; + case 'F': + data = FLOAT; + break; + case 'J': + data = LONG; + break; + case 'D': + data = DOUBLE; + break; + // case 'L': + default: + // stores the internal name, not the descriptor + t = desc.substring(dims + 1, desc.length() - 1); + data = OBJECT | cw.addType(t); + } + return dims - index << 28 | data; + } + } + + /** + * Pops a type from the output frame stack and returns its value. + * + * @return the type that has been popped from the output frame stack. + */ + private int pop() { + owner.inputStackTop--; + if (outputStackTop > 0) { + return outputStack[outputStackTop]; + } else { + // if the output frame stack is empty, pops from the input stack + return STACK | -owner.inputStackTop; + } + } + + /** + * Pops the given number of types from the output frame stack. + * + * @param elements the number of types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // if the number of elements to be popped is greater than the number + // of elements in the output stack, clear it, and pops the remaining + // elements from the input stack. + owner.inputStackTop -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops a type from the output frame stack. + * + * @param desc the descriptor of the type to be popped. Can also be a method + * descriptor (in this case this method pops the types corresponding + * to the method arguments). + */ + private void pop(final String desc) { + final char c = desc.charAt(0); + if (c == '(') { + pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); + } else if (c == 'J' || c == 'D') { + pop(2); + } else { + pop(1); + } + } + + /** + * Adds a new type to the list of types on which a constructor is invoked in + * the basic block. + * + * @param var a type on a which a constructor is invoked. + */ + private void init(final int var) { + // creates and/or resizes the initializations array if necessary + if (initializations == null) { + initializations = new int[2]; + } + final int n = initializations.length; + if (initializationCount >= n) { + final int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; + System.arraycopy(initializations, 0, t, 0, n); + initializations = t; + } + // stores the type to be initialized + initializations[initializationCount++] = var; + } + + /** + * Replaces the given type with the appropriate type if it is one of the + * types on which a constructor is invoked in the basic block. + * + * @param cw the ClassWriter to which this label belongs. + * @param t a type + * @return t or, if t is one of the types on which a constructor is invoked + * in the basic block, the type corresponding to this constructor. + */ + private int init(final ClassWriter cw, final int t) { + int s; + if (t == UNINITIALIZED_THIS) { + s = OBJECT | cw.addType(cw.thisName); + } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { + final String type = cw.typeTable[t & BASE_VALUE].strVal1; + s = OBJECT | cw.addType(type); + } else { + return t; + } + for (int j = 0; j < initializationCount; ++j) { + int u = initializations[j]; + final int dim = u & DIM; + final int kind = u & KIND; + if (kind == LOCAL) { + u = dim + inputLocals[u & VALUE]; + } else if (kind == STACK) { + u = dim + inputStack[inputStack.length - (u & VALUE)]; + } + if (t == u) { + return s; + } + } + return t; + } + + /** + * Initializes the input frame of the first basic block from the method + * descriptor. + * + * @param cw the ClassWriter to which this label belongs. + * @param access the access flags of the method to which this label belongs. + * @param args the formal parameter types of this method. + * @param maxLocals the maximum number of local variables of this method. + */ + void initInputFrame( + final ClassWriter cw, + final int access, + final Type[] args, + final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int i = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { + inputLocals[i++] = OBJECT | cw.addType(cw.thisName); + } else { + inputLocals[i++] = UNINITIALIZED_THIS; + } + } + for (int j = 0; j < args.length; ++j) { + final int t = type(cw, args[j].getDescriptor()); + inputLocals[i++] = t; + if (t == LONG || t == DOUBLE) { + inputLocals[i++] = TOP; + } + } + while (i < maxLocals) { + inputLocals[i++] = TOP; + } + } + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode the opcode of the instruction. + * @param arg the operand of the instruction, if any. + * @param cw the class writer to which this label belongs. + * @param item the operand of the instructions, if any. + */ + void execute( + final int opcode, + final int arg, + final ClassWriter cw, + final Item item) { + int t1, t2, t3, t4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (item.type) { + case ClassWriter.INT: + push(INTEGER); + break; + case ClassWriter.LONG: + push(LONG); + push(TOP); + break; + case ClassWriter.FLOAT: + push(FLOAT); + break; + case ClassWriter.DOUBLE: + push(DOUBLE); + push(TOP); + break; + case ClassWriter.CLASS: + push(OBJECT | cw.addType("java/lang/Class")); + break; + // case ClassWriter.STR: + default: + push(OBJECT | cw.addType("java/lang/String")); + } + break; + case Opcodes.ALOAD: + push(get(arg)); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INTEGER); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + pop(2); + push(FLOAT); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + t1 = pop(); + push(ELEMENT_OF + t1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + t1 = pop(); + set(arg, t1); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + t1 = pop(); + set(arg, t1); + set(arg + 1, TOP); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + t1 = pop(); + push(t1); + push(t1); + break; + case Opcodes.DUP_X1: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2: + t1 = pop(); + t2 = pop(); + push(t2); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X1: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t2); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.SWAP: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + break; + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + set(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new RuntimeException("JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(cw, item.strVal3); + break; + case Opcodes.PUTSTATIC: + pop(item.strVal3); + break; + case Opcodes.GETFIELD: + pop(1); + push(cw, item.strVal3); + break; + case Opcodes.PUTFIELD: + pop(item.strVal3); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(item.strVal3); + if (opcode != Opcodes.INVOKESTATIC) { + t1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL + && item.strVal2.charAt(0) == '<') { + init(t1); + } + } + push(cw, item.strVal3); + break; + case Opcodes.INVOKEDYNAMIC: + pop(item.strVal2); + push(cw, item.strVal2); + break; + case Opcodes.NEW: + push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + // case Opcodes.T_LONG: + default: + push(ARRAY_OF | LONG); + break; + } + break; + case Opcodes.ANEWARRAY: + String s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, '[' + s); + } else { + push(ARRAY_OF | OBJECT | cw.addType(s)); + } + break; + case Opcodes.CHECKCAST: + s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, s); + } else { + push(OBJECT | cw.addType(s)); + } + break; + // case Opcodes.MULTIANEWARRAY: + default: + pop(arg); + push(cw, item.strVal1); + break; + } + } + + /** + * Merges the input frame of the given basic block with the input and output + * frames of this basic block. Returns true if the input frame of + * the given label has been changed by this operation. + * + * @param cw the ClassWriter to which this label belongs. + * @param frame the basic block whose input frame must be updated. + * @param edge the kind of the {@link Edge} between this label and 'label'. + * See {@link Edge#info}. + * @return true if the input frame of the given label has been + * changed by this operation. + */ + boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + boolean changed = false; + int i, s, dim, kind, t; + + final int nLocal = inputLocals.length; + final int nStack = inputStack.length; + if (frame.inputLocals == null) { + frame.inputLocals = new int[nLocal]; + changed = true; + } + + for (i = 0; i < nLocal; ++i) { + if (outputLocals != null && i < outputLocals.length) { + s = outputLocals[i]; + if (s == 0) { + t = inputLocals[i]; + } else { + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + } + } else { + t = inputLocals[i]; + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputLocals, i); + } + + if (edge > 0) { + for (i = 0; i < nLocal; ++i) { + t = inputLocals[i]; + changed |= merge(cw, t, frame.inputLocals, i); + } + if (frame.inputStack == null) { + frame.inputStack = new int[1]; + changed = true; + } + changed |= merge(cw, edge, frame.inputStack, 0); + return changed; + } + + final int nInputStack = inputStack.length + owner.inputStackTop; + if (frame.inputStack == null) { + frame.inputStack = new int[nInputStack + outputStackTop]; + changed = true; + } + + for (i = 0; i < nInputStack; ++i) { + t = inputStack[i]; + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, i); + } + for (i = 0; i < outputStackTop; ++i) { + s = outputStack[i]; + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } + return changed; + } + + /** + * Merges the type at the given index in the given type array with the given + * type. Returns true if the type array has been modified by this + * operation. + * + * @param cw the ClassWriter to which this label belongs. + * @param t the type with which the type array element must be merged. + * @param types an array of types. + * @param index the index of the type that must be merged in 'types'. + * @return true if the type array has been modified by this + * operation. + */ + private static boolean merge( + final ClassWriter cw, + int t, + final int[] types, + final int index) { + final int u = types[index]; + if (u == t) { + // if the types are equal, merge(u,t)=u, so there is no change + return false; + } + if ((t & ~DIM) == NULL) { + if (u == NULL) { + return false; + } + t = NULL; + } + if (u == 0) { + // if types[index] has never been assigned, merge(u,t)=t + types[index] = t; + return true; + } + int v; + if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { + // if u is a reference type of any dimension + if (t == NULL) { + // if t is the NULL type, merge(u,t)=u, so there is no change + return false; + } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + if ((u & BASE_KIND) == OBJECT) { + // if t is also a reference type, and if u and t have the + // same dimension merge(u,t) = dim(t) | common parent of the + // element types of u and t + v = t & DIM | OBJECT + | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); + } else { + // if u and t are array types, but not with the same element + // type, merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } + } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { + // if t is any other reference or array type, + // merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } else { + // if t is any other type, merge(u,t)=TOP + v = TOP; + } + } else if (u == NULL) { + // if u is the NULL type, merge(u,t)=t, + // or TOP if t is not a reference type + v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; + } else { + // if u is any other type, merge(u,t)=TOP whatever t + v = TOP; + } + if (u != v) { + types[index] = v; + return true; + } + return false; + } +} diff --git a/src/org/rsbot/loader/asm/Handler.java b/src/org/rsbot/loader/asm/Handler.java new file mode 100644 index 0000000..f2b42dd --- /dev/null +++ b/src/org/rsbot/loader/asm/Handler.java @@ -0,0 +1,46 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * Information about an exception handler block. + * + * @author Eric Bruneton + */ +class Handler { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + Label start; + + /** + * End of the exception handler's scope (exclusive). + */ + Label end; + + /** + * Beginning of the exception handler's code. + */ + Label handler; + + /** + * Internal name of the type of exceptions handled by this handler, or + * null to catch any exceptions. + */ + String desc; + + /** + * Constant pool index of the internal name of the type of exceptions + * handled by this handler, or 0 to catch any exceptions. + */ + int type; + + /** + * Next exception handler block info. + */ + Handler next; +} diff --git a/src/org/rsbot/loader/asm/Item.java b/src/org/rsbot/loader/asm/Item.java new file mode 100644 index 0000000..3673bba --- /dev/null +++ b/src/org/rsbot/loader/asm/Item.java @@ -0,0 +1,229 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A constant pool item. Constant pool items can be created with the 'newXXX' + * methods in the {@link ClassWriter} class. + * + * @author Eric Bruneton + */ +final class Item { + + /** + * Index of this item in the constant pool. + */ + int index; + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is one of {@link ClassWriter#INT}, + * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, + * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, + * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, + * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, + * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}. + *

+ * Special Item types are used for Items that are stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. These special item types are + * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and + * {@link ClassWriter#TYPE_MERGED}. + */ + int type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal3; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + /** + * Link to another constant pool item, used for collision lists in the + * constant pool's hash table. + */ + Item next; + + /** + * Constructs an uninitialized {@link Item}. + */ + Item() { + } + + /** + * Constructs an uninitialized {@link Item} for constant pool element at + * given position. + * + * @param index index of the item to be constructed. + */ + Item(final int index) { + this.index = index; + } + + /** + * Constructs a copy of the given item. + * + * @param index index of the item to be constructed. + * @param i the item that must be copied into the item to be constructed. + */ + Item(final int index, final Item i) { + this.index = index; + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + strVal3 = i.strVal3; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal the value of this item. + */ + void set(final int intVal) { + type = ClassWriter.INT; + this.intVal = intVal; + hashCode = 0x7FFFFFFF & type + intVal; + } + + /** + * Sets this item to a long item. + * + * @param longVal the value of this item. + */ + void set(final long longVal) { + type = ClassWriter.LONG; + this.longVal = longVal; + hashCode = 0x7FFFFFFF & type + (int) longVal; + } + + /** + * Sets this item to a float item. + * + * @param floatVal the value of this item. + */ + void set(final float floatVal) { + type = ClassWriter.FLOAT; + intVal = Float.floatToRawIntBits(floatVal); + hashCode = 0x7FFFFFFF & type + (int) floatVal; + } + + /** + * Sets this item to a double item. + * + * @param doubleVal the value of this item. + */ + void set(final double doubleVal) { + type = ClassWriter.DOUBLE; + longVal = Double.doubleToRawLongBits(doubleVal); + hashCode = 0x7FFFFFFF & type + (int) doubleVal; + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type the type of this item. + * @param strVal1 first part of the value of this item. + * @param strVal2 second part of the value of this item. + * @param strVal3 third part of the value of this item. + */ + void set( + final int type, + final String strVal1, + final String strVal2, + final String strVal3) { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.strVal3 = strVal3; + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.TYPE_NORMAL: + hashCode = 0x7FFFFFFF & type + strVal1.hashCode(); + return; + case ClassWriter.NAME_TYPE: + hashCode = 0x7FFFFFFF & type + strVal1.hashCode() + * strVal2.hashCode(); + return; + // ClassWriter.FIELD: + // ClassWriter.METH: + // ClassWriter.IMETH: + default: + hashCode = 0x7FFFFFFF & type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode(); + } + } + + /** + * Indicates if the given item is equal to this one. This method assumes + * that the two items have the same {@link #type}. + * + * @param i the item to be compared to this one. Both items must have the + * same {@link #type}. + * @return true if the given item if equal to this one, + * false otherwise. + */ + boolean isEqualTo(final Item i) { + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.TYPE_NORMAL: + return i.strVal1.equals(strVal1); + case ClassWriter.TYPE_MERGED: + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + return i.longVal == longVal; + case ClassWriter.INT: + case ClassWriter.FLOAT: + return i.intVal == intVal; + case ClassWriter.TYPE_UNINIT: + return i.intVal == intVal && i.strVal1.equals(strVal1); + case ClassWriter.NAME_TYPE: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); + // case ClassWriter.FIELD: + // case ClassWriter.METH: + // case ClassWriter.IMETH: + default: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) + && i.strVal3.equals(strVal3); + } + } + +} diff --git a/src/org/rsbot/loader/asm/Label.java b/src/org/rsbot/loader/asm/Label.java new file mode 100644 index 0000000..af45780 --- /dev/null +++ b/src/org/rsbot/loader/asm/Label.java @@ -0,0 +1,496 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A label represents a position in the bytecode of a method. Labels are used + * for jump, goto, and switch instructions, and for try catch blocks. A label + * designates the instruction that is just after. Note however that + * there can be other elements between a label and the instruction it + * designates (such as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * Indicates if this label is only used for debug attributes. Such a label + * is not the start of a basic block, the target of a jump instruction, or + * an exception handler. It can be safely ignored in control flow graph + * analysis algorithms (for optimization purposes). + */ + static final int DEBUG = 1; + + /** + * Indicates if the position of this label is known. + */ + static final int RESOLVED = 2; + + /** + * Indicates if this label has been updated, after instruction resizing. + */ + static final int RESIZED = 4; + + /** + * Indicates if this basic block has been pushed in the basic block stack. + * See {@link MethodWriter#visitMaxs visitMaxs}. + */ + static final int PUSHED = 8; + + /** + * Indicates if this label is the target of a jump instruction, or the start + * of an exception handler. + */ + static final int TARGET = 16; + + /** + * Indicates if a stack map frame must be stored for this label. + */ + static final int STORE = 32; + + /** + * Indicates if this label corresponds to a reachable basic block. + */ + static final int REACHABLE = 64; + + /** + * Indicates if this basic block ends with a JSR instruction. + */ + static final int JSR = 128; + + /** + * Indicates if this basic block ends with a RET instruction. + */ + static final int RET = 256; + + /** + * Indicates if this basic block is the start of a subroutine. + */ + static final int SUBROUTINE = 512; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(null, ...) call. + */ + static final int VISITED = 1024; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(!null, ...) call. + */ + static final int VISITED2 = 2048; + + /** + * Field used to associate user information to a label. Warning: this field + * is used by the ASM tree package. In order to use it with the ASM tree + * package you must override the MethodNode#getLabelNode method. + */ + public Object info; + + /** + * Flags that indicate the status of this label. + * + * @see #DEBUG + * @see #RESOLVED + * @see #RESIZED + * @see #PUSHED + * @see #TARGET + * @see #STORE + * @see #REACHABLE + * @see #JSR + * @see #RET + */ + int status; + + /** + * The line number corresponding to this label, if known. + */ + int line; + + /** + * The position of this label in the code, if known. + */ + int position; + + /** + * Number of forward references to this label, times two. + */ + private int referenceCount; + + /** + * Information about forward references. Each forward reference is + * described by two consecutive integers in this array: the first one is the + * position of the first byte of the bytecode instruction that contains the + * forward reference, while the second is the position of the first byte of + * the forward reference itself. In fact the sign of the first integer + * indicates if this reference uses 2 or 4 bytes, and its absolute value + * gives the position of the bytecode instruction. This array is also used + * as a bitset to store the subroutines to which a basic block belongs. This + * information is needed in {@linked MethodWriter#visitMaxs}, after all + * forward references have been resolved. Hence the same array can be used + * for both purposes without problems. + */ + private int[] srcAndRefPositions; + + // ------------------------------------------------------------------------ + + /** + * Start of the output stack relatively to the input stack. The exact + * semantics of this field depends on the algorithm that is used. + *

+ * When only the maximum stack size is computed, this field is the number of + * elements in the input stack. + *

+ * When the stack map frames are completely computed, this field is the + * offset of the first output stack element relatively to the top of the + * input stack. This offset is always negative or null. A null offset means + * that the output stack must be appended to the input stack. A -n offset + * means that the first n output stack elements must replace the top n input + * stack elements, and that the other elements must be appended to the input + * stack. + */ + int inputStackTop; + + /** + * Maximum height reached by the output stack, relatively to the top of the + * input stack. This maximum is always positive or null. + */ + int outputStackMax; + + /** + * Information about the input and output stack map frames of this basic + * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} + * option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited. This linked + * list does not include labels used for debug info only. If + * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it + * does not contain successive labels that denote the same bytecode position + * (in this case only the first label appears in this list). + */ + Label successor; + + /** + * The successors of this node in the control flow graph. These successors + * are stored in a linked list of {@link Edge Edge} objects, linked to each + * other by their {@link Edge#next} field. + */ + Edge successors; + + /** + * The next basic block in the basic block stack. This stack is used in the + * main loop of the fix point algorithm used in the second step of the + * control flow analysis algorithms. It is also used in + * {@link #visitSubroutine} to avoid using a recursive method. + * + * @see MethodWriter#visitMaxs + */ + Label next; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new label. + */ + public Label() { + } + + // ------------------------------------------------------------------------ + // Methods to compute offsets and to manage forward references + // ------------------------------------------------------------------------ + + /** + * Returns the offset corresponding to this label. This offset is computed + * from the start of the method's bytecode. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @return the offset corresponding to this label. + * @throws IllegalStateException if this label is not resolved yet. + */ + public int getOffset() { + if ((status & RESOLVED) == 0) { + throw new IllegalStateException("Label offset position has not been resolved yet"); + } + return position; + } + + /** + * Puts a reference to this label in the bytecode of a method. If the + * position of the label is known, the offset is computed and written + * directly. Otherwise, a null offset is written and a new forward reference + * is declared for this label. + * + * @param owner the code writer that calls this method. + * @param out the bytecode of the method. + * @param source the position of first byte of the bytecode instruction that + * contains this label. + * @param wideOffset true if the reference must be stored in 4 + * bytes, or false if it must be stored with 2 bytes. + * @throws IllegalArgumentException if this label has not been created by + * the given code writer. + */ + void put( + final MethodWriter owner, + final ByteVector out, + final int source, + final boolean wideOffset) { + if ((status & RESOLVED) == 0) { + if (wideOffset) { + addReference(-1 - source, out.length); + out.putInt(-1); + } else { + addReference(source, out.length); + out.putShort(-1); + } + } else { + if (wideOffset) { + out.putInt(position - source); + } else { + out.putShort(position - source); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only + * for a true forward reference, i.e. only if this label is not resolved + * yet. For backward references, the offset of the reference can be, and + * must be, computed and stored directly. + * + * @param sourcePosition the position of the referencing instruction. This + * position will be used to compute the offset of this forward + * reference. + * @param referencePosition the position where the offset for this forward + * reference must be stored. + */ + private void addReference( + final int sourcePosition, + final int referencePosition) { + if (srcAndRefPositions == null) { + srcAndRefPositions = new int[6]; + } + if (referenceCount >= srcAndRefPositions.length) { + final int[] a = new int[srcAndRefPositions.length + 6]; + System.arraycopy(srcAndRefPositions, + 0, + a, + 0, + srcAndRefPositions.length); + srcAndRefPositions = a; + } + srcAndRefPositions[referenceCount++] = sourcePosition; + srcAndRefPositions[referenceCount++] = referencePosition; + } + + /** + * Resolves all forward references to this label. This method must be called + * when this label is added to the bytecode of the method, i.e. when its + * position becomes known. This method fills in the blanks that where left + * in the bytecode by each forward reference previously added to this label. + * + * @param owner the code writer that calls this method. + * @param position the position of this label in the bytecode. + * @param data the bytecode of the method. + * @return true if a blank that was left for this label was to + * small to store the offset. In such a case the corresponding jump + * instruction is replaced with a pseudo instruction (using unused + * opcodes) using an unsigned two bytes offset. These pseudo + * instructions will need to be replaced with true instructions with + * wider offsets (4 bytes instead of 2). This is done in + * {@link MethodWriter#resizeInstructions}. + * @throws IllegalArgumentException if this label has already been resolved, + * or if it has not been created by the given code writer. + */ + boolean resolve( + final MethodWriter owner, + final int position, + final byte[] data) { + boolean needUpdate = false; + status |= RESOLVED; + this.position = position; + int i = 0; + while (i < referenceCount) { + final int source = srcAndRefPositions[i++]; + int reference = srcAndRefPositions[i++]; + int offset; + if (source >= 0) { + offset = position - source; + if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { + /* + * changes the opcode of the jump instruction, in order to + * be able to find it later (see resizeInstructions in + * MethodWriter). These temporary opcodes are similar to + * jump instruction opcodes, except that the 2 bytes offset + * is unsigned (and can therefore represent values from 0 to + * 65535, which is sufficient since the size of a method is + * limited to 65535 bytes). + */ + final int opcode = data[reference - 1] & 0xFF; + if (opcode <= Opcodes.JSR) { + // changes IFEQ ... JSR to opcodes 202 to 217 + data[reference - 1] = (byte) (opcode + 49); + } else { + // changes IFNULL and IFNONNULL to opcodes 218 and 219 + data[reference - 1] = (byte) (opcode + 20); + } + needUpdate = true; + } + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } else { + offset = position + source + 1; + data[reference++] = (byte) (offset >>> 24); + data[reference++] = (byte) (offset >>> 16); + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } + } + return needUpdate; + } + + /** + * Returns the first label of the series to which this label belongs. For an + * isolated label or for the first label in a series of successive labels, + * this method returns the label itself. For other labels it returns the + * first label of the series. + * + * @return the first label of the series to which this label belongs. + */ + Label getFirst() { + return !ClassReader.FRAMES || frame == null ? this : frame.owner; + } + + // ------------------------------------------------------------------------ + // Methods related to subroutines + // ------------------------------------------------------------------------ + + /** + * Returns true is this basic block belongs to the given subroutine. + * + * @param id a subroutine id. + * @return true is this basic block belongs to the given subroutine. + */ + boolean inSubroutine(final long id) { + return (status & Label.VISITED) != 0 && (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; + } + + /** + * Returns true if this basic block and the given one belong to a common + * subroutine. + * + * @param block another basic block. + * @return true if this basic block and the given one belong to a common + * subroutine. + */ + boolean inSameSubroutine(final Label block) { + if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { + return false; + } + for (int i = 0; i < srcAndRefPositions.length; ++i) { + if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { + return true; + } + } + return false; + } + + /** + * Marks this basic block as belonging to the given subroutine. + * + * @param id a subroutine id. + * @param nbSubroutines the total number of subroutines in the method. + */ + void addToSubroutine(final long id, final int nbSubroutines) { + if ((status & VISITED) == 0) { + status |= VISITED; + srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; + } + srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + } + + /** + * Finds the basic blocks that belong to a given subroutine, and marks these + * blocks as belonging to this subroutine. This method follows the control + * flow graph to find all the blocks that are reachable from the current + * block WITHOUT following any JSR target. + * + * @param JSR a JSR block that jumps to this subroutine. If this JSR is not + * null it is added to the successor of the RET blocks found in the + * subroutine. + * @param id the id of this subroutine. + * @param nbSubroutines the total number of subroutines in the method. + */ + void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) { + // user managed stack of labels, to avoid using a recursive method + // (recursivity can lead to stack overflow with very large methods) + Label stack = this; + while (stack != null) { + // removes a label l from the stack + final Label l = stack; + stack = l.next; + l.next = null; + + if (JSR != null) { + if ((l.status & VISITED2) != 0) { + continue; + } + l.status |= VISITED2; + // adds JSR to the successors of l, if it is a RET block + if ((l.status & RET) != 0) { + if (!l.inSameSubroutine(JSR)) { + final Edge e = new Edge(); + e.info = l.inputStackTop; + e.successor = JSR.successors.successor; + e.next = l.successors; + l.successors = e; + } + } + } else { + // if the l block already belongs to subroutine 'id', continue + if (l.inSubroutine(id)) { + continue; + } + // marks the l block as belonging to subroutine 'id' + l.addToSubroutine(id, nbSubroutines); + } + // pushes each successor of l on the stack, except JSR targets + Edge e = l.successors; + while (e != null) { + // if the l block is a JSR block, then 'l.successors.next' leads + // to the JSR target (see {@link #visitJumpInsn}) and must + // therefore not be followed + if ((l.status & Label.JSR) == 0 || e != l.successors.next) { + // pushes e.successor on the stack if it not already added + if (e.successor.next == null) { + e.successor.next = stack; + stack = e.successor; + } + } + e = e.next; + } + } + } + + // ------------------------------------------------------------------------ + // Overridden Object methods + // ------------------------------------------------------------------------ + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/src/org/rsbot/loader/asm/MethodAdapter.java b/src/org/rsbot/loader/asm/MethodAdapter.java new file mode 100644 index 0000000..6974dfa --- /dev/null +++ b/src/org/rsbot/loader/asm/MethodAdapter.java @@ -0,0 +1,186 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * An empty {@link MethodVisitor} that delegates to another + * {@link MethodVisitor}. This class can be used as a super class to quickly + * implement usefull method adapter classes, just by overriding the necessary + * methods. + * + * @author Eric Bruneton + */ +public class MethodAdapter implements MethodVisitor { + + /** + * The {@link MethodVisitor} to which this adapter delegates calls. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodAdapter} object. + * + * @param mv the code visitor to which this adapter must delegate calls. + */ + public MethodAdapter(final MethodVisitor mv) { + this.mv = mv; + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return mv.visitAnnotationDefault(); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) { + return mv.visitAnnotation(desc, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + + @Override + public void visitAttribute(final Attribute attr) { + mv.visitAttribute(attr); + } + + @Override + public void visitCode() { + mv.visitCode(); + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + + @Override + public void visitInsn(final int opcode) { + mv.visitInsn(opcode); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + mv.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + mv.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + mv.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + mv.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(final Label label) { + mv.visitLabel(label); + } + + @Override + public void visitLdcInsn(final Object cst) { + mv.visitLdcInsn(cst); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + mv.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label[] labels) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + mv.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) { + mv.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + mv.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + mv.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + mv.visitEnd(); + } +} diff --git a/src/org/rsbot/loader/asm/MethodVisitor.java b/src/org/rsbot/loader/asm/MethodVisitor.java new file mode 100644 index 0000000..81a9159 --- /dev/null +++ b/src/org/rsbot/loader/asm/MethodVisitor.java @@ -0,0 +1,375 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A visitor to visit a Java method. The methods of this interface must be + * called in the following order: [ visitAnnotationDefault ] ( + * visitAnnotation | visitParameterAnnotation | + * visitAttribute )* [ visitCode ( visitFrame | + * visitXInsn | visitLabel | visitTryCatchBlock | + * visitLocalVariable | visitLineNumber)* visitMaxs ] + * visitEnd. In addition, the visitXInsn + * and visitLabel methods must be called in the sequential order of + * the bytecode instructions of the visited code, visitTryCatchBlock + * must be called before the labels passed as arguments have been + * visited, and the visitLocalVariable and visitLineNumber + * methods must be called after the labels passed as arguments have been + * visited. + * + * @author Eric Bruneton + */ +public interface MethodVisitor { + + // ------------------------------------------------------------------------- + // Annotations and non standard attributes + // ------------------------------------------------------------------------- + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this + * annotation interface method, or null if this visitor + * is not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are + * ignored. Moreover, exacly one visit method must be called on this + * annotation visitor, followed by visitEnd. + */ + AnnotationVisitor visitAnnotationDefault(); + + /** + * Visits an annotation of this method. + * + * @param desc the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + AnnotationVisitor visitAnnotation(String desc, boolean visible); + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter the parameter index. + * @param desc the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + AnnotationVisitor visitParameterAnnotation( + int parameter, + String desc, + boolean visible); + + /** + * Visits a non standard attribute of this method. + * + * @param attr an attribute. + */ + void visitAttribute(Attribute attr); + + /** + * Starts the visit of the method's code, if any (i.e. non abstract method). + */ + void visitCode(); + + /** + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called just before any + * instruction i that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements just + * before i is executed.

(*) this is mandatory only + * for classes whose version is greater than or equal to + * {@link Opcodes#V1_6 V1_6}.

Packed frames are basically + * "deltas" from the state of the previous frame (very first frame is + * implicitly defined by the method's parameters and access flags):

    + *
  • {@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.
  • {@link Opcodes#F_SAME1} + * representing frame with exactly the same locals as the previous frame and + * with single value on the stack (nStack is 1 and + * stack[0] contains value for the type of the stack item).
  • + *
  • {@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (nLocal is 1, 2 or 3 and + * local elements contains values representing added types).
  • + *
  • {@link Opcodes#F_CHOP} representing frame with current locals are + * the same as the locals in the previous frame, except that the last 1-3 + * locals are absent and with the empty stack (nLocals is 1, + * 2 or 3).
  • {@link Opcodes#F_FULL} representing complete frame + * data.
+ * + * @param type the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed + * frames. + * @param nLocal the number of local variables in the visited frame. + * @param local the local variable types in this frame. This array must not + * be modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are represented + * by String objects (representing internal names), and uninitialized + * types by Label objects (this label designates the NEW instruction + * that created this uninitialized value). + * @param nStack the number of operand stack elements in the visited frame. + * @param stack the operand stack types in this frame. This array must not + * be modified. Its content has the same format as the "local" array. + * @throw IllegalStateException if a frame is visited just after another + * one, without any instruction between the two (unless this frame + * is a Opcodes#F_SAME frame, in which case it is silently ignored). + */ + void visitFrame( + int type, + int nLocal, + Object[] local, + int nStack, + Object[] stack); + + // ------------------------------------------------------------------------- + // Normal instructions + // ------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode the opcode of the instruction to be visited. This opcode is + * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, + * ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, + * FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, + * DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, + * DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, + * DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, + * DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, + * LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, + * I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, + * I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, + * FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, + * MONITORENTER, or MONITOREXIT. + */ + void visitInsn(int opcode); + + /** + * Visits an instruction with a single int operand. + * + * @param opcode the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand the operand of the instruction to be visited.
When + * opcode is BIPUSH, operand value should be between Byte.MIN_VALUE + * and Byte.MAX_VALUE.
When opcode is SIPUSH, operand value + * should be between Short.MIN_VALUE and Short.MAX_VALUE.
When + * opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + void visitIntInsn(int opcode, int operand); + + /** + * Visits a local variable instruction. A local variable instruction is an + * instruction that loads or stores the value of a local variable. + * + * @param opcode the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, + * LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var the operand of the instruction to be visited. This operand is + * the index of a local variable. + */ + void visitVarInsn(int opcode, int var); + + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see {@link + * Type#getInternalName() getInternalName}). + */ + void visitTypeInsn(int opcode, String type); + + /** + * Visits a field instruction. A field instruction is an instruction that + * loads or stores the value of a field of an object. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see {@link + * Type#getInternalName() getInternalName}). + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type Type}). + */ + void visitFieldInsn(int opcode, String owner, String name, String desc); + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, + * INVOKEINTERFACE or INVOKEDYNAMIC. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName() getInternalName}) + * or {@link org.rsbot.loader.asm.Opcodes#INVOKEDYNAMIC_OWNER}. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + */ + void visitMethodInsn(int opcode, String owner, String name, String desc); + + /** + * Visits a jump instruction. A jump instruction is an instruction that may + * jump to another instruction. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, + * IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be visited. This operand + * is a label that designates the instruction to which the jump + * instruction may jump. + */ + void visitJumpInsn(int opcode, Label label); + + /** + * Visits a label. A label designates the instruction that will be visited + * just after it. + * + * @param label a {@link Label Label} object. + */ + void visitLabel(Label label); + + // ------------------------------------------------------------------------- + // Special instructions + // ------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. + * + * @param cst the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} a {@link String} (or a {@link Type} for + * .class constants, for classes whose version is 49.0 or + * more). + */ + void visitLdcInsn(Object cst); + + /** + * Visits an IINC instruction. + * + * @param var index of the local variable to be incremented. + * @param increment amount to increment the local variable by. + */ + void visitIincInsn(int var, int increment); + + /** + * Visits a TABLESWITCH instruction. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. labels[i] is + * the beginning of the handler block for the min + i key. + */ + void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels); + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. labels[i] is + * the beginning of the handler block for the keys[i] key. + */ + void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels); + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param desc an array type descriptor (see {@link Type Type}). + * @param dims number of dimensions of the array to allocate. + */ + void visitMultiANewArrayInsn(String desc, int dims); + + // ------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start beginning of the exception handler's scope (inclusive). + * @param end end of the exception handler's scope (exclusive). + * @param handler beginning of the exception handler's code. + * @param type internal name of the type of exceptions handled by the + * handler, or null to catch any exceptions (for "finally" + * blocks). + * @throws IllegalArgumentException if one of the labels has already been + * visited by this visitor (by the {@link #visitLabel visitLabel} + * method). + */ + void visitTryCatchBlock(Label start, Label end, Label handler, String type); + + /** + * Visits a local variable declaration. + * + * @param name the name of a local variable. + * @param desc the type descriptor of this local variable. + * @param signature the type signature of this local variable. May be + * null if the local variable type does not use generic + * types. + * @param start the first instruction corresponding to the scope of this + * local variable (inclusive). + * @param end the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index the local variable's index. + * @throws IllegalArgumentException if one of the labels has not already + * been visited by this visitor (by the + * {@link #visitLabel visitLabel} method). + */ + void visitLocalVariable( + String name, + String desc, + String signature, + Label start, + Label end, + int index); + + /** + * Visits a line number declaration. + * + * @param line a line number. This number refers to the source file from + * which the class was compiled. + * @param start the first instruction corresponding to this line number. + * @throws IllegalArgumentException if start has not already been + * visited by this visitor (by the {@link #visitLabel visitLabel} + * method). + */ + void visitLineNumber(int line, Label start); + + /** + * Visits the maximum stack size and the maximum number of local variables + * of the method. + * + * @param maxStack maximum stack size of the method. + * @param maxLocals maximum number of local variables for the method. + */ + void visitMaxs(int maxStack, int maxLocals); + + /** + * Visits the end of the method. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the method have been visited. + */ + void visitEnd(); +} diff --git a/src/org/rsbot/loader/asm/MethodWriter.java b/src/org/rsbot/loader/asm/MethodWriter.java new file mode 100644 index 0000000..af743f4 --- /dev/null +++ b/src/org/rsbot/loader/asm/MethodWriter.java @@ -0,0 +1,2533 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * A {@link MethodVisitor} that generates methods in bytecode form. Each visit + * method of this class appends the bytecode corresponding to the visited + * instruction to a byte vector, in the order these methods are called. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +class MethodWriter implements MethodVisitor { + + /** + * Pseudo access flag used to denote constructors. + */ + static final int ACC_CONSTRUCTOR = 262144; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. + */ + static final int SAME_FRAME = 0; // to 63 (0-3f) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1 + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) + + /** + * Reserved for future use + */ + static final int RESERVED = 128; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1. Offset is bigger then 63; + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that the k last locals are absent. The value of k is given + * by the formula 251-frame_type. + */ + static final int CHOP_FRAME = 248; // to 250 (f8-fA) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. Offset is bigger then 63; + */ + static final int SAME_FRAME_EXTENDED = 251; // fb + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that k additional locals are defined. The value of k is + * given by the formula frame_type-251. + */ + static final int APPEND_FRAME = 252; // to 254 // fc-fe + + /** + * Full frame + */ + static final int FULL_FRAME = 255; // ff + + /** + * Indicates that the stack map frames must be recomputed from scratch. In + * this case the maximum stack size and number of local variables is also + * recomputed from scratch. + * + * @see #compute + */ + private static final int FRAMES = 0; + + /** + * Indicates that the maximum stack size and number of local variables must + * be automatically computed. + * + * @see #compute + */ + private static final int MAXS = 1; + + /** + * Indicates that nothing must be automatically computed. + * + * @see #compute + */ + private static final int NOTHING = 2; + + /** + * Next method writer (see {@link ClassWriter#firstMethod firstMethod}). + */ + MethodWriter next; + + /** + * The class writer to which this method must be added. + */ + final ClassWriter cw; + + /** + * Access flags of this method. + */ + private int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * method. + */ + private final int desc; + + /** + * The descriptor of this method. + */ + private final String descriptor; + + /** + * The signature of this method. + */ + String signature; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the index of the first byte to copied from + * cw.cr.b. + */ + int classReaderOffset; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the number of bytes to copied from + * cw.cr.b. + */ + int classReaderLength; + + /** + * Number of exceptions that can be thrown by this method. + */ + int exceptionCount; + + /** + * The exceptions that can be thrown by this method. More precisely, this + * array contains the indexes of the constant pool items that contain the + * internal names of these exception classes. + */ + int[] exceptions; + + /** + * The annotation default attribute of this method. May be null. + */ + private ByteVector annd; + + /** + * The runtime visible annotations of this method. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this method. May be null. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] panns; + + /** + * The runtime invisible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] ipanns; + + /** + * The number of synthetic parameters of this method. + */ + private int synthetics; + + /** + * The non standard attributes of the method. + */ + private Attribute attrs; + + /** + * The bytecode of this method. + */ + private ByteVector code = new ByteVector(); + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + /** + * Maximum number of local variables for this method. + */ + private int maxLocals; + + /** + * Number of stack map frames in the StackMapTable attribute. + */ + private int frameCount; + + /** + * The StackMapTable attribute. + */ + private ByteVector stackMap; + + /** + * The offset of the last frame that was written in the StackMapTable + * attribute. + */ + private int previousFrameOffset; + + /** + * The last frame that was written in the StackMapTable attribute. + * + * @see #frame + */ + private int[] previousFrame; + + /** + * Index of the next element to be added in {@link #frame}. + */ + private int frameIndex; + + /** + * The current stack map frame. The first element contains the offset of the + * instruction to which the frame corresponds, the second element is the + * number of locals and the third one is the number of stack elements. The + * local variables start at index 3 and are followed by the operand stack + * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = + * nStack, frame[3] = nLocal. All types are encoded as integers, with the + * same format as the one used in {@link Label}, but limited to BASE types. + */ + private int[] frame; + + /** + * Number of elements in the exception handler list. + */ + private int handlerCount; + + /** + * The first element in the exception handler list. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list. + */ + private Handler lastHandler; + + /** + * Number of entries in the LocalVariableTable attribute. + */ + private int localVarCount; + + /** + * The LocalVariableTable attribute. + */ + private ByteVector localVar; + + /** + * Number of entries in the LocalVariableTypeTable attribute. + */ + private int localVarTypeCount; + + /** + * The LocalVariableTypeTable attribute. + */ + private ByteVector localVarType; + + /** + * Number of entries in the LineNumberTable attribute. + */ + private int lineNumberCount; + + /** + * The LineNumberTable attribute. + */ + private ByteVector lineNumber; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; + + /** + * Indicates if some jump instructions are too small and need to be resized. + */ + private boolean resize; + + /** + * The number of subroutines in this method. + */ + private int subroutines; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow graph analysis algorithm (used to compute the + * maximum stack size). A control flow graph contains one node per "basic + * block", and one edge per "jump" from one basic block to another. Each + * node (i.e., each basic block) is represented by the Label object that + * corresponds to the first instruction of this basic block. Each node also + * stores the list of its successors in the graph, as a linked list of Edge + * objects. + */ + + /** + * Indicates what must be automatically computed. + * + * @see #FRAMES + * @see #MAXS + * @see #NOTHING + */ + private final int compute; + + /** + * A list of labels. This list is the list of basic blocks in the method, + * i.e. a list of Label objects linked to each other by their + * {@link Label#successor} field, in the order they are visited by + * {@link MethodVisitor#visitLabel}, and starting with the first basic block. + */ + private Label labels; + + /** + * The previous basic block. + */ + private Label previousBlock; + + /** + * The current basic block. + */ + private Label currentBlock; + + /** + * The (relative) stack size after the last visited instruction. This size + * is relative to the beginning of the current basic block, i.e., the true + * stack size after the last visited instruction is equal to the + * {@link Label#inputStackTop beginStackSize} of the current basic block + * plus stackSize. + */ + private int stackSize; + + /** + * The (relative) maximum stack size after the last visited instruction. + * This size is relative to the beginning of the current basic block, i.e., + * the true maximum stack size after the last visited instruction is equal + * to the {@link Label#inputStackTop beginStackSize} of the current basic + * block plus stackSize. + */ + private int maxStackSize; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link MethodWriter}. + * + * @param cw the class writer in which the method must be added. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be null. + * @param exceptions the internal names of the method's exceptions. May be + * null. + * @param computeMaxs true if the maximum stack size and number + * of local variables must be automatically computed. + * @param computeFrames true if the stack map tables must be + * recomputed from scratch. + */ + MethodWriter( + final ClassWriter cw, + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions, + final boolean computeMaxs, + final boolean computeFrames) { + if (cw.firstMethod == null) { + cw.firstMethod = this; + } else { + cw.lastMethod.next = this; + } + cw.lastMethod = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + descriptor = desc; + if (ClassReader.SIGNATURES) { + this.signature = signature; + } + if (exceptions != null && exceptions.length > 0) { + exceptionCount = exceptions.length; + this.exceptions = new int[exceptionCount]; + for (int i = 0; i < exceptionCount; ++i) { + this.exceptions[i] = cw.newClass(exceptions[i]); + } + } + compute = computeFrames ? FRAMES : computeMaxs ? MAXS : NOTHING; + if (computeMaxs || computeFrames) { + if (computeFrames && "".equals(name)) { + this.access |= ACC_CONSTRUCTOR; + } + // updates maxLocals + int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --size; + } + maxLocals = size; + // creates and visits the label for the first basic block + labels = new Label(); + labels.status |= Label.PUSHED; + visitLabel(labels); + } + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor interface + // ------------------------------------------------------------------------ + + public AnnotationVisitor visitAnnotationDefault() { + if (!ClassReader.ANNOTATIONS) { + return null; + } + annd = new ByteVector(); + return new AnnotationWriter(cw, false, annd, null, 0); + } + + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + final ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + final AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + final ByteVector bv = new ByteVector(); + if ("Ljava/lang/Synthetic;".equals(desc)) { + // workaround for a bug in javac with synthetic parameters + // see ClassReader.readParameterAnnotations + synthetics = Math.max(synthetics, parameter + 1); + return new AnnotationWriter(cw, false, bv, null, 0); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + final AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + if (panns == null) { + panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = panns[parameter]; + panns[parameter] = aw; + } else { + if (ipanns == null) { + ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = ipanns[parameter]; + ipanns[parameter] = aw; + } + return aw; + } + + public void visitAttribute(final Attribute attr) { + if (attr.isCodeAttribute()) { + attr.next = cattrs; + cattrs = attr; + } else { + attr.next = attrs; + attrs = attr; + } + } + + public void visitCode() { + } + + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) { + if (!ClassReader.FRAMES || compute == FRAMES) { + return; + } + + if (type == Opcodes.F_NEW) { + startFrame(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + if (local[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) local[i]); + } else if (local[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) local[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) local[i]).position); + } + } + for (int i = 0; i < nStack; ++i) { + if (stack[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) stack[i]); + } else if (stack[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) stack[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) stack[i]).position); + } + } + endFrame(); + } else { + int delta; + if (stackMap == null) { + stackMap = new ByteVector(); + delta = code.length; + } else { + delta = code.length - previousFrameOffset - 1; + if (delta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + stackMap.putByte(FULL_FRAME) + .putShort(delta) + .putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + stackMap.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + writeFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + stackMap.putByte(SAME_FRAME_EXTENDED + nLocal) + .putShort(delta); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + stackMap.putByte(SAME_FRAME_EXTENDED - nLocal) + .putShort(delta); + break; + case Opcodes.F_SAME: + if (delta < 64) { + stackMap.putByte(delta); + } else { + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + } + break; + case Opcodes.F_SAME1: + if (delta < 64) { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + } else { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + } + writeFrameType(stack[0]); + break; + } + + previousFrameOffset = code.length; + ++frameCount; + } + } + + public void visitInsn(final int opcode) { + // adds the instruction to the bytecode of the method + code.putByte(opcode); + // update currentBlock + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + } else { + // updates current and max stack sizes + final int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + // if opcode == ATHROW or xRETURN, ends current block (no successor) + if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN + || opcode == Opcodes.ATHROW) { + noSuccessor(); + } + } + } + + public void visitIntInsn(final int opcode, final int operand) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // updates current and max stack sizes only for NEWARRAY + // (stack size variation = 0 for BIPUSH or SIPUSH) + final int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + } + + public void visitVarInsn(final int opcode, final int var) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, var, null, null); + } else { + // updates current and max stack sizes + if (opcode == Opcodes.RET) { + // no stack change, but end of current block (no successor) + currentBlock.status |= Label.RET; + // save 'stackSize' here for future use + // (see {@link #findSubroutineSuccessors}) + currentBlock.inputStackTop = stackSize; + noSuccessor(); + } else { // xLOAD or xSTORE + final int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + } + if (compute != NOTHING) { + // updates max locals + int n; + if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { + n = var + 2; + } else { + n = var + 1; + } + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var < 4 && opcode != Opcodes.RET) { + int opt; + if (opcode < Opcodes.ISTORE) { + /* ILOAD_0 */ + opt = 26 + (opcode - Opcodes.ILOAD << 2) + var; + } else { + /* ISTORE_0 */ + opt = 59 + (opcode - Opcodes.ISTORE << 2) + var; + } + code.putByte(opt); + } else if (var >= 256) { + code.putByte(196 /* WIDE */).put12(opcode, var); + } else { + code.put11(opcode, var); + } + if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { + visitLabel(new Label()); + } + } + + public void visitTypeInsn(final int opcode, final String type) { + final Item i = cw.newClassItem(type); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, code.length, cw, i); + } else if (opcode == Opcodes.NEW) { + // updates current and max stack sizes only if opcode == NEW + // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) + final int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) { + final Item i = cw.newFieldItem(owner, name, desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + int size; + // computes the stack size variation + final char c = desc.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); + break; + // case Constants.PUTFIELD: + default: + size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); + break; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) { + final boolean itf = opcode == Opcodes.INVOKEINTERFACE; + final Item i = opcode == Opcodes.INVOKEDYNAMIC ? + cw.newNameTypeItem(name, desc) : + cw.newMethodItem(owner, name, desc, itf); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size; + if (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKEDYNAMIC) { + size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + } else { + size = stackSize - (argSize >> 2) + (argSize & 0x03); + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (itf) { + if (argSize == 0) { + argSize = Type.getArgumentsAndReturnSizes(desc); + i.intVal = argSize; + } + code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); + } else { + code.put12(opcode, i.index); + if (opcode == Opcodes.INVOKEDYNAMIC) { + code.putShort(0); + } + } + } + + public void visitJumpInsn(final int opcode, final Label label) { + Label nextInsn = null; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + // 'label' is the target of a jump instruction + label.getFirst().status |= Label.TARGET; + // adds 'label' as a successor of this basic block + addSuccessor(Edge.NORMAL, label); + if (opcode != Opcodes.GOTO) { + // creates a Label for the next basic block + nextInsn = new Label(); + } + } else { + if (opcode == Opcodes.JSR) { + if ((label.status & Label.SUBROUTINE) == 0) { + label.status |= Label.SUBROUTINE; + ++subroutines; + } + currentBlock.status |= Label.JSR; + addSuccessor(stackSize + 1, label); + // creates a Label for the next basic block + nextInsn = new Label(); + /* + * note that, by construction in this method, a JSR block + * has at least two successors in the control flow graph: + * the first one leads the next instruction after the JSR, + * while the second one leads to the JSR target. + */ + } else { + // updates current stack size (max stack size unchanged + // because stack size variation always negative in this + // case) + stackSize += Frame.SIZE[opcode]; + addSuccessor(stackSize, label); + } + } + } + // adds the instruction to the bytecode of the method + if ((label.status & Label.RESOLVED) != 0 + && label.position - code.length < Short.MIN_VALUE) { + /* + * case of a backward jump with an offset < -32768. In this case we + * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx + * with IFNOTxxx GOTO_W , where IFNOTxxx is the + * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where + * designates the instruction just after the GOTO_W. + */ + if (opcode == Opcodes.GOTO) { + code.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + code.putByte(201); // JSR_W + } else { + // if the IF instruction is transformed into IFNOT GOTO_W the + // next instruction becomes the target of the IFNOT instruction + if (nextInsn != null) { + nextInsn.status |= Label.TARGET; + } + code.putByte(opcode <= 166 + ? (opcode + 1 ^ 1) - 1 + : opcode ^ 1); + code.putShort(8); // jump offset + code.putByte(200); // GOTO_W + } + label.put(this, code, code.length - 1, true); + } else { + /* + * case of a backward jump with an offset >= -32768, or of a forward + * jump with, of course, an unknown offset. In these cases we store + * the offset in 2 bytes (which will be increased in + * resizeInstructions, if needed). + */ + code.putByte(opcode); + label.put(this, code, code.length - 1, false); + } + if (currentBlock != null) { + if (nextInsn != null) { + // if the jump instruction is not a GOTO, the next instruction + // is also a successor of this instruction. Calling visitLabel + // adds the label of this next instruction as a successor of the + // current block, and starts a new basic block + visitLabel(nextInsn); + } + if (opcode == Opcodes.GOTO) { + noSuccessor(); + } + } + } + + public void visitLabel(final Label label) { + // resolves previous forward references to label, if any + resize |= label.resolve(this, code.length, code.data); + // updates currentBlock + if ((label.status & Label.DEBUG) != 0) { + return; + } + if (compute == FRAMES) { + if (currentBlock != null) { + if (label.position == currentBlock.position) { + // successive labels, do not start a new basic block + currentBlock.status |= label.status & Label.TARGET; + label.frame = currentBlock.frame; + return; + } + // ends current block (with one new successor) + addSuccessor(Edge.NORMAL, label); + } + // begins a new current block + currentBlock = label; + if (label.frame == null) { + label.frame = new Frame(); + label.frame.owner = label; + } + // updates the basic block list + if (previousBlock != null) { + if (label.position == previousBlock.position) { + previousBlock.status |= label.status & Label.TARGET; + label.frame = previousBlock.frame; + currentBlock = previousBlock; + return; + } + previousBlock.successor = label; + } + previousBlock = label; + } else if (compute == MAXS) { + if (currentBlock != null) { + // ends current block (with one new successor) + currentBlock.outputStackMax = maxStackSize; + addSuccessor(stackSize, label); + } + // begins a new current block + currentBlock = label; + // resets the relative current and max stack sizes + stackSize = 0; + maxStackSize = 0; + // updates the basic block list + if (previousBlock != null) { + previousBlock.successor = label; + } + previousBlock = label; + } + } + + public void visitLdcInsn(final Object cst) { + final Item i = cw.newConstItem(cst); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); + } else { + int size; + // computes the stack size variation + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + size = stackSize + 2; + } else { + size = stackSize + 1; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + final int index = i.index; + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + code.put12(20 /* LDC2_W */, index); + } else if (index >= 256) { + code.put12(19 /* LDC_W */, index); + } else { + code.put11(Opcodes.LDC, index); + } + } + + public void visitIincInsn(final int var, final int increment) { + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.IINC, var, null, null); + } + } + if (compute != NOTHING) { + // updates max locals + final int n = var + 1; + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var > 255 || increment > 127 || increment < -128) { + code.putByte(196 /* WIDE */) + .put12(Opcodes.IINC, var) + .putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + } + + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label[] labels) { + // adds the instruction to the bytecode of the method + final int source = code.length; + code.putByte(Opcodes.TABLESWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(min).putInt(max); + for (int i = 0; i < labels.length; ++i) { + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) { + // adds the instruction to the bytecode of the method + final int source = code.length; + code.putByte(Opcodes.LOOKUPSWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // adds current block successors + addSuccessor(Edge.NORMAL, dflt); + dflt.getFirst().status |= Label.TARGET; + for (int i = 0; i < labels.length; ++i) { + addSuccessor(Edge.NORMAL, labels[i]); + labels[i].getFirst().status |= Label.TARGET; + } + } else { + // updates current stack size (max stack size unchanged) + --stackSize; + // adds current block successors + addSuccessor(stackSize, dflt); + for (int i = 0; i < labels.length; ++i) { + addSuccessor(stackSize, labels[i]); + } + } + // ends current block + noSuccessor(); + } + } + + public void visitMultiANewArrayInsn(final String desc, final int dims) { + final Item i = cw.newClassItem(desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); + } else { + // updates current stack size (max stack size unchanged because + // stack size variation always negative or null) + stackSize += 1 - dims; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); + } + + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) { + ++handlerCount; + final Handler h = new Handler(); + h.start = start; + h.end = end; + h.handler = handler; + h.desc = type; + h.type = type != null ? cw.newClass(type) : 0; + if (lastHandler == null) { + firstHandler = h; + } else { + lastHandler.next = h; + } + lastHandler = h; + } + + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) { + if (signature != null) { + if (localVarType == null) { + localVarType = new ByteVector(); + } + ++localVarTypeCount; + localVarType.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)) + .putShort(cw.newUTF8(signature)) + .putShort(index); + } + if (localVar == null) { + localVar = new ByteVector(); + } + ++localVarCount; + localVar.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)) + .putShort(cw.newUTF8(desc)) + .putShort(index); + if (compute != NOTHING) { + // updates max locals + final char c = desc.charAt(0); + final int n = index + (c == 'J' || c == 'D' ? 2 : 1); + if (n > maxLocals) { + maxLocals = n; + } + } + } + + public void visitLineNumber(final int line, final Label start) { + if (lineNumber == null) { + lineNumber = new ByteVector(); + } + ++lineNumberCount; + lineNumber.putShort(start.position); + lineNumber.putShort(line); + } + + public void visitMaxs(final int maxStack, final int maxLocals) { + if (ClassReader.FRAMES && compute == FRAMES) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start.getFirst(); + final Label h = handler.handler.getFirst(); + final Label e = handler.end.getFirst(); + // computes the kind of the edges to 'h' + final String t = handler.desc == null + ? "java/lang/Throwable" + : handler.desc; + final int kind = Frame.OBJECT | cw.addType(t); + // h is an exception handler + h.status |= Label.TARGET; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + final Edge b = new Edge(); + b.info = kind; + b.successor = h; + // adds it to the successors of 'l' + b.next = l.successors; + l.successors = b; + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + // creates and visits the first (implicit) frame + Frame f = labels.frame; + final Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, this.maxLocals); + visitFrame(f); + + /* + * fix point algorithm: mark the first basic block as 'changed' + * (i.e. put it in the 'changed' list) and, while there are changed + * basic blocks, choose one, mark it as unchanged, and update its + * successors (which can be changed in the process). + */ + int max = 0; + Label changed = labels; + while (changed != null) { + // removes a basic block from the list of changed basic blocks + final Label l = changed; + changed = changed.next; + l.next = null; + f = l.frame; + // a reachable jump target must be stored in the stack map + if ((l.status & Label.TARGET) != 0) { + l.status |= Label.STORE; + } + // all visited labels are reachable, by definition + l.status |= Label.REACHABLE; + // updates the (absolute) maximum stack size + final int blockMax = f.inputStack.length + l.outputStackMax; + if (blockMax > max) { + max = blockMax; + } + // updates the successors of the current basic block + Edge e = l.successors; + while (e != null) { + final Label n = e.successor.getFirst(); + final boolean change = f.merge(cw, n.frame, e.info); + if (change && n.next == null) { + // if n has changed and is not already in the 'changed' + // list, adds it to this list + n.next = changed; + changed = n; + } + e = e.next; + } + } + + // visits all the frames that must be stored in the stack map + Label l = labels; + while (l != null) { + f = l.frame; + if ((l.status & Label.STORE) != 0) { + visitFrame(f); + } + if ((l.status & Label.REACHABLE) == 0) { + // finds start and end of dead basic block + final Label k = l.successor; + final int start = l.position; + final int end = (k == null ? code.length : k.position) - 1; + // if non empty basic block + if (end >= start) { + max = Math.max(max, 1); + // replaces instructions with NOP ... NOP ATHROW + for (int i = start; i < end; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[end] = (byte) Opcodes.ATHROW; + // emits a frame for this unreachable block + startFrame(start, 0, 1); + frame[frameIndex++] = Frame.OBJECT + | cw.addType("java/lang/Throwable"); + endFrame(); + } + } + l = l.successor; + } + + this.maxStack = max; + } else if (compute == MAXS) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start; + final Label h = handler.handler; + final Label e = handler.end; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + final Edge b = new Edge(); + b.info = Edge.EXCEPTION; + b.successor = h; + // adds it to the successors of 'l' + if ((l.status & Label.JSR) == 0) { + b.next = l.successors; + l.successors = b; + } else { + // if l is a JSR block, adds b after the first two edges + // to preserve the hypothesis about JSR block successors + // order (see {@link #visitJumpInsn}) + b.next = l.successors.next.next; + l.successors.next.next = b; + } + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + if (subroutines > 0) { + // completes the control flow graph with the RET successors + /* + * first step: finds the subroutines. This step determines, for + * each basic block, to which subroutine(s) it belongs. + */ + // finds the basic blocks that belong to the "main" subroutine + int id = 0; + labels.visitSubroutine(null, 1, subroutines); + // finds the basic blocks that belong to the real subroutines + Label l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + // the subroutine is defined by l's TARGET, not by l + final Label subroutine = l.successors.next.successor; + // if this subroutine has not been visited yet... + if ((subroutine.status & Label.VISITED) == 0) { + // ...assigns it a new id and finds its basic blocks + id += 1; + subroutine.visitSubroutine(null, id / 32L << 32 + | 1L << id % 32, subroutines); + } + } + l = l.successor; + } + // second step: finds the successors of RET blocks + l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + Label L = labels; + while (L != null) { + L.status &= ~Label.VISITED2; + L = L.successor; + } + // the subroutine is defined by l's TARGET, not by l + final Label subroutine = l.successors.next.successor; + subroutine.visitSubroutine(l, 0, subroutines); + } + l = l.successor; + } + } + + /* + * control flow analysis algorithm: while the block stack is not + * empty, pop a block from this stack, update the max stack size, + * compute the true (non relative) begin stack size of the + * successors of this block, and push these successors onto the + * stack (unless they have already been pushed onto the stack). + * Note: by hypothesis, the {@link Label#inputStackTop} of the + * blocks in the block stack are the true (non relative) beginning + * stack sizes of these blocks. + */ + int max = 0; + Label stack = labels; + while (stack != null) { + // pops a block from the stack + Label l = stack; + stack = stack.next; + // computes the true (non relative) max stack size of this block + final int start = l.inputStackTop; + final int blockMax = start + l.outputStackMax; + // updates the global max stack size + if (blockMax > max) { + max = blockMax; + } + // analyzes the successors of the block + Edge b = l.successors; + if ((l.status & Label.JSR) != 0) { + // ignores the first edge of JSR blocks (virtual successor) + b = b.next; + } + while (b != null) { + l = b.successor; + // if this successor has not already been pushed... + if ((l.status & Label.PUSHED) == 0) { + // computes its true beginning stack size... + l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start + + b.info; + // ...and pushes it onto the stack + l.status |= Label.PUSHED; + l.next = stack; + stack = l; + } + b = b.next; + } + } + this.maxStack = max; + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods: control flow analysis algorithm + // ------------------------------------------------------------------------ + + /** + * Adds a successor to the {@link #currentBlock currentBlock} block. + * + * @param info information about the control flow edge to be added. + * @param successor the successor block to be added to the current block. + */ + private void addSuccessor(final int info, final Label successor) { + // creates and initializes an Edge object... + final Edge b = new Edge(); + b.info = info; + b.successor = successor; + // ...and adds it to the successor list of the currentBlock block + b.next = currentBlock.successors; + currentBlock.successors = b; + } + + /** + * Ends the current basic block. This method must be used in the case where + * the current basic block does not have any successor. + */ + private void noSuccessor() { + if (compute == FRAMES) { + final Label l = new Label(); + l.frame = new Frame(); + l.frame.owner = l; + l.resolve(this, code.length, code.data); + previousBlock.successor = l; + previousBlock = l; + } else { + currentBlock.outputStackMax = maxStackSize; + } + currentBlock = null; + } + + // ------------------------------------------------------------------------ + // Utility methods: stack map frames + // ------------------------------------------------------------------------ + + /** + * Visits a frame that has been computed from scratch. + * + * @param f the frame that must be visited. + */ + private void visitFrame(final Frame f) { + int i, t; + int nTop = 0; + int nLocal = 0; + int nStack = 0; + final int[] locals = f.inputLocals; + final int[] stacks = f.inputStack; + // computes the number of locals (ignores TOP types that are just after + // a LONG or a DOUBLE, and all trailing TOP types) + for (i = 0; i < locals.length; ++i) { + t = locals[i]; + if (t == Frame.TOP) { + ++nTop; + } else { + nLocal += nTop + 1; + nTop = 0; + } + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // computes the stack size (ignores TOP types that are just after + // a LONG or a DOUBLE) + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + ++nStack; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // visits the frame and its content + startFrame(f.owner.position, nLocal, nStack); + for (i = 0; nLocal > 0; ++i, --nLocal) { + t = locals[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + endFrame(); + } + + /** + * Starts the visit of a stack map frame. + * + * @param offset the offset of the instruction to which the frame + * corresponds. + * @param nLocal the number of local variables in the frame. + * @param nStack the number of stack elements in the frame. + */ + private void startFrame(final int offset, final int nLocal, final int nStack) { + final int n = 3 + nLocal + nStack; + if (frame == null || frame.length < n) { + frame = new int[n]; + } + frame[0] = offset; + frame[1] = nLocal; + frame[2] = nStack; + frameIndex = 3; + } + + /** + * Checks if the visit of the current frame {@link #frame} is finished, and + * if yes, write it in the StackMapTable attribute. + */ + private void endFrame() { + if (previousFrame != null) { // do not write the first frame + if (stackMap == null) { + stackMap = new ByteVector(); + } + writeFrame(); + ++frameCount; + } + previousFrame = frame; + frame = null; + } + + /** + * Compress and writes the current frame {@link #frame} in the StackMapTable + * attribute. + */ + private void writeFrame() { + final int clocalsSize = frame[1]; + final int cstackSize = frame[2]; + if ((cw.version & 0xFFFF) < Opcodes.V1_6) { + stackMap.putShort(frame[0]).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + return; + } + int localsSize = previousFrame[1]; + int type = FULL_FRAME; + int k = 0; + int delta; + if (frameCount == 0) { + delta = frame[0]; + } else { + delta = frame[0] - previousFrame[0] - 1; + } + if (cstackSize == 0) { + k = clocalsSize - localsSize; + switch (k) { + case -3: + case -2: + case -1: + type = CHOP_FRAME; + localsSize = clocalsSize; + break; + case 0: + type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = APPEND_FRAME; + break; + } + } else if (clocalsSize == localsSize && cstackSize == 1) { + type = delta < 63 + ? SAME_LOCALS_1_STACK_ITEM_FRAME + : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != FULL_FRAME) { + // verify if locals are the same + int l = 3; + for (int j = 0; j < localsSize; j++) { + if (frame[l] != previousFrame[l]) { + type = FULL_FRAME; + break; + } + l++; + } + } + switch (type) { + case SAME_FRAME: + stackMap.putByte(delta); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_FRAME_EXTENDED: + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + break; + case CHOP_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + break; + case APPEND_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + writeFrameTypes(3 + localsSize, 3 + clocalsSize); + break; + // case FULL_FRAME: + default: + stackMap.putByte(FULL_FRAME) + .putShort(delta) + .putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + } + } + + /** + * Writes some types of the current frame {@link #frame} into the + * StackMapTableAttribute. This method converts types from the format used + * in {@link Label} to the format used in StackMapTable attributes. In + * particular, it converts type table indexes to constant pool indexes. + * + * @param start index of the first type in {@link #frame} to write. + * @param end index of last type in {@link #frame} to write (exclusive). + */ + private void writeFrameTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + final int t = frame[i]; + int d = t & Frame.DIM; + if (d == 0) { + final int v = t & Frame.BASE_VALUE; + switch (t & Frame.BASE_KIND) { + case Frame.OBJECT: + stackMap.putByte(7) + .putShort(cw.newClass(cw.typeTable[v].strVal1)); + break; + case Frame.UNINITIALIZED: + stackMap.putByte(8).putShort(cw.typeTable[v].intVal); + break; + default: + stackMap.putByte(v); + } + } else { + final StringBuffer buf = new StringBuffer(); + d >>= 28; + while (d-- > 0) { + buf.append('['); + } + if ((t & Frame.BASE_KIND) == Frame.OBJECT) { + buf.append('L'); + buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + buf.append(';'); + } else { + switch (t & 0xF) { + case 1: + buf.append('I'); + break; + case 2: + buf.append('F'); + break; + case 3: + buf.append('D'); + break; + case 9: + buf.append('Z'); + break; + case 10: + buf.append('B'); + break; + case 11: + buf.append('C'); + break; + case 12: + buf.append('S'); + break; + default: + buf.append('J'); + } + } + stackMap.putByte(7).putShort(cw.newClass(buf.toString())); + } + } + } + + private void writeFrameType(final Object type) { + if (type instanceof String) { + stackMap.putByte(7).putShort(cw.newClass((String) type)); + } else if (type instanceof Integer) { + stackMap.putByte(((Integer) type).intValue()); + } else { + stackMap.putByte(8).putShort(((Label) type).position); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: dump bytecode array + // ------------------------------------------------------------------------ + + /** + * Returns the size of the bytecode of this method. + * + * @return the size of the bytecode of this method. + */ + final int getSize() { + if (classReaderOffset != 0) { + return 6 + classReaderLength; + } + if (resize) { + // replaces the temporary jump opcodes introduced by Label.resolve. + if (ClassReader.RESIZE) { + resizeInstructions(); + } else { + throw new RuntimeException("Method code too large!"); + } + } + int size = 8; + if (code.length > 0) { + cw.newUTF8("Code"); + size += 18 + code.length + 8 * handlerCount; + if (localVar != null) { + cw.newUTF8("LocalVariableTable"); + size += 8 + localVar.length; + } + if (localVarType != null) { + cw.newUTF8("LocalVariableTypeTable"); + size += 8 + localVarType.length; + } + if (lineNumber != null) { + cw.newUTF8("LineNumberTable"); + size += 8 + lineNumber.length; + } + if (stackMap != null) { + final boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + cw.newUTF8(zip ? "StackMapTable" : "StackMap"); + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, + code.data, + code.length, + maxStack, + maxLocals); + } + } + if (exceptionCount > 0) { + cw.newUTF8("Exceptions"); + size += 8 + 2 * exceptionCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + cw.newUTF8("Synthetic"); + size += 6; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != null) { + cw.newUTF8("Signature"); + cw.newUTF8(signature); + size += 8; + } + if (ClassReader.ANNOTATIONS && annd != null) { + cw.newUTF8("AnnotationDefault"); + size += 6 + annd.length; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && panns != null) { + cw.newUTF8("RuntimeVisibleParameterAnnotations"); + size += 7 + 2 * (panns.length - synthetics); + for (int i = panns.length - 1; i >= synthetics; --i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + cw.newUTF8("RuntimeInvisibleParameterAnnotations"); + size += 7 + 2 * (ipanns.length - synthetics); + for (int i = ipanns.length - 1; i >= synthetics; --i) { + size += ipanns[i] == null ? 0 : ipanns[i].getSize(); + } + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the bytecode of this method in the given byte vector. + * + * @param out the byte vector into which the bytecode of this method must be + * copied. + */ + final void put(final ByteVector out) { + final int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(desc); + if (classReaderOffset != 0) { + out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); + return; + } + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (exceptionCount > 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + ++attributeCount; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && annd != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && panns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (code.length > 0) { + int size = 12 + code.length + 8 * handlerCount; + if (localVar != null) { + size += 8 + localVar.length; + } + if (localVarType != null) { + size += 8 + localVarType.length; + } + if (lineNumber != null) { + size += 8 + lineNumber.length; + } + if (stackMap != null) { + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, + code.data, + code.length, + maxStack, + maxLocals); + } + out.putShort(cw.newUTF8("Code")).putInt(size); + out.putShort(maxStack).putShort(maxLocals); + out.putInt(code.length).putByteArray(code.data, 0, code.length); + out.putShort(handlerCount); + if (handlerCount > 0) { + Handler h = firstHandler; + while (h != null) { + out.putShort(h.start.position) + .putShort(h.end.position) + .putShort(h.handler.position) + .putShort(h.type); + h = h.next; + } + } + attributeCount = 0; + if (localVar != null) { + ++attributeCount; + } + if (localVarType != null) { + ++attributeCount; + } + if (lineNumber != null) { + ++attributeCount; + } + if (stackMap != null) { + ++attributeCount; + } + if (cattrs != null) { + attributeCount += cattrs.getCount(); + } + out.putShort(attributeCount); + if (localVar != null) { + out.putShort(cw.newUTF8("LocalVariableTable")); + out.putInt(localVar.length + 2).putShort(localVarCount); + out.putByteArray(localVar.data, 0, localVar.length); + } + if (localVarType != null) { + out.putShort(cw.newUTF8("LocalVariableTypeTable")); + out.putInt(localVarType.length + 2).putShort(localVarTypeCount); + out.putByteArray(localVarType.data, 0, localVarType.length); + } + if (lineNumber != null) { + out.putShort(cw.newUTF8("LineNumberTable")); + out.putInt(lineNumber.length + 2).putShort(lineNumberCount); + out.putByteArray(lineNumber.data, 0, lineNumber.length); + } + if (stackMap != null) { + final boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); + out.putInt(stackMap.length + 2).putShort(frameCount); + out.putByteArray(stackMap.data, 0, stackMap.length); + } + if (cattrs != null) { + cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); + } + } + if (exceptionCount > 0) { + out.putShort(cw.newUTF8("Exceptions")) + .putInt(2 * exceptionCount + 2); + out.putShort(exceptionCount); + for (int i = 0; i < exceptionCount; ++i) { + out.putShort(exceptions[i]); + } + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != null) { + out.putShort(cw.newUTF8("Signature")) + .putInt(2) + .putShort(cw.newUTF8(signature)); + } + if (ClassReader.ANNOTATIONS && annd != null) { + out.putShort(cw.newUTF8("AnnotationDefault")); + out.putInt(annd.length); + out.putByteArray(annd.data, 0, annd.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && panns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); + AnnotationWriter.put(panns, synthetics, out); + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); + AnnotationWriter.put(ipanns, synthetics, out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) + // ------------------------------------------------------------------------ + + /** + * Resizes and replaces the temporary instructions inserted by + * {@link Label#resolve} for wide forward jumps, while keeping jump offsets + * and instruction addresses consistent. This may require to resize other + * existing instructions, or even to introduce new instructions: for + * example, increasing the size of an instruction by 2 at the middle of a + * method can increases the offset of an IFEQ instruction from 32766 to + * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W + * 32765. This, in turn, may require to increase the size of another jump + * instruction, and so on... All these operations are handled automatically + * by this method.

This method must be called after all the method + * that is being built has been visited. In particular, the + * {@link Label Label} objects used to construct the method are no longer + * valid after this method has been called. + */ + private void resizeInstructions() { + byte[] b = code.data; // bytecode of the method + int u, v, label; // indexes in b + int i, j; // loop indexes + /* + * 1st step: As explained above, resizing an instruction may require to + * resize another one, which may require to resize yet another one, and + * so on. The first step of the algorithm consists in finding all the + * instructions that need to be resized, without modifying the code. + * This is done by the following "fix point" algorithm: + * + * Parse the code to find the jump instructions whose offset will need + * more than 2 bytes to be stored (the future offset is computed from + * the current offset and from the number of bytes that will be inserted + * or removed between the source and target instructions). For each such + * instruction, adds an entry in (a copy of) the indexes and sizes + * arrays (if this has not already been done in a previous iteration!). + * + * If at least one entry has been added during the previous step, go + * back to the beginning, otherwise stop. + * + * In fact the real algorithm is complicated by the fact that the size + * of TABLESWITCH and LOOKUPSWITCH instructions depends on their + * position in the bytecode (because of padding). In order to ensure the + * convergence of the algorithm, the number of bytes to be added or + * removed from these instructions is over estimated during the previous + * loop, and computed exactly only after the loop is finished (this + * requires another pass to parse the bytecode of the method). + */ + int[] allIndexes = new int[0]; // copy of indexes + int[] allSizes = new int[0]; // copy of sizes + boolean[] resize; // instructions to be resized + int newOffset; // future offset of a jump instruction + + resize = new boolean[code.length]; + + // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done + int state = 3; + do { + if (state == 3) { + state = 2; + } + u = 0; + while (u < b.length) { + int opcode = b[u] & 0xFF; // opcode of current instruction + int insert = 0; // bytes to be added after this instruction + + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // converts temporary opcodes 202 to 217, 218 and + // 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (newOffset < Short.MIN_VALUE + || newOffset > Short.MAX_VALUE) { + if (!resize[u]) { + if (opcode == Opcodes.GOTO + || opcode == Opcodes.JSR) { + // two additional bytes will be required to + // replace this GOTO or JSR instruction with + // a GOTO_W or a JSR_W + insert = 2; + } else { + // five additional bytes will be required to + // replace this IFxxx instruction with + // IFNOTxxx GOTO_W , where IFNOTxxx + // is the "opposite" opcode of IFxxx (i.e., + // IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + insert = 5; + } + resize[u] = true; + } + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + u += 5; + break; + case ClassWriter.TABL_INSN: + if (state == 1) { + // true number of bytes to be added (or removed) + // from this instruction = (future number of padding + // bytes - current number of padding byte) - + // previously over estimated variation = + // = ((3 - newOffset%4) - (3 - u%4)) - u%4 + // = (-newOffset%4 + u%4) - u%4 + // = -(newOffset & 3) + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // over estimation of the number of bytes to be + // added to this instruction = 3 - current number + // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; + break; + case ClassWriter.LOOK_INSN: + if (state == 1) { + // like TABL_INSN + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // like TABL_INSN + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 8 * readInt(b, u + 4) + 8; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFDYNMETH_INSN: + u += 5; + break; + // case ClassWriter.MANA_INSN: + default: + u += 4; + break; + } + if (insert != 0) { + // adds a new (u, insert) entry in the allIndexes and + // allSizes arrays + final int[] newIndexes = new int[allIndexes.length + 1]; + final int[] newSizes = new int[allSizes.length + 1]; + System.arraycopy(allIndexes, + 0, + newIndexes, + 0, + allIndexes.length); + System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); + newIndexes[allIndexes.length] = u; + newSizes[allSizes.length] = insert; + allIndexes = newIndexes; + allSizes = newSizes; + if (insert > 0) { + state = 3; + } + } + } + if (state < 3) { + --state; + } + } while (state != 0); + + // 2nd step: + // copies the bytecode of the method into a new bytevector, updates the + // offsets, and inserts (or removes) bytes as requested. + + final ByteVector newCode = new ByteVector(code.length); + + u = 0; + while (u < code.length) { + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + newCode.putByte(opcode); + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (resize[u]) { + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // with IFNOTxxx GOTO_W , where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO) { + newCode.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + newCode.putByte(201); // JSR_W + } else { + newCode.putByte(opcode <= 166 + ? (opcode + 1 ^ 1) - 1 + : opcode ^ 1); + newCode.putShort(8); // jump offset + newCode.putByte(200); // GOTO_W + // newOffset now computed from start of GOTO_W + newOffset -= 3; + } + newCode.putInt(newOffset); + } else { + newCode.putByte(opcode); + newCode.putShort(newOffset); + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + label = u + readInt(b, u + 1); + newOffset = getNewOffset(allIndexes, allSizes, u, label); + newCode.putByte(opcode); + newCode.putInt(newOffset); + u += 5; + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.TABLESWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + j = readInt(b, u) - j + 1; + u += 4; + newCode.putInt(readInt(b, u - 4)); + for (; j > 0; --j) { + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.LOOKUPSWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + for (; j > 0; --j) { + newCode.putInt(readInt(b, u)); + u += 4; + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + newCode.putByteArray(b, u, 6); + u += 6; + } else { + newCode.putByteArray(b, u, 4); + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + newCode.putByteArray(b, u, 2); + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + newCode.putByteArray(b, u, 3); + u += 3; + break; + case ClassWriter.ITFDYNMETH_INSN: + newCode.putByteArray(b, u, 5); + u += 5; + break; + // case MANA_INSN: + default: + newCode.putByteArray(b, u, 4); + u += 4; + break; + } + } + + // recomputes the stack map frames + if (frameCount > 0) { + if (compute == FRAMES) { + frameCount = 0; + stackMap = null; + previousFrame = null; + frame = null; + final Frame f = new Frame(); + f.owner = labels; + final Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, maxLocals); + visitFrame(f); + Label l = labels; + while (l != null) { + /* + * here we need the original label position. getNewOffset + * must therefore never have been called for this label. + */ + u = l.position - 3; + if ((l.status & Label.STORE) != 0 || u >= 0 && resize[u]) { + getNewOffset(allIndexes, allSizes, l); + // TODO update offsets in UNINITIALIZED values + visitFrame(l.frame); + } + l = l.successor; + } + } else { + /* + * Resizing an existing stack map frame table is really hard. + * Not only the table must be parsed to update the offets, but + * new frames may be needed for jump instructions that were + * inserted by this method. And updating the offsets or + * inserting frames can change the format of the following + * frames, in case of packed frames. In practice the whole table + * must be recomputed. For this the frames are marked as + * potentially invalid. This will cause the whole class to be + * reread and rewritten with the COMPUTE_FRAMES option (see the + * ClassWriter.toByteArray method). This is not very efficient + * but is much easier and requires much less code than any other + * method I can think of. + */ + cw.invalidFrames = true; + } + } + // updates the exception handler block labels + Handler h = firstHandler; + while (h != null) { + getNewOffset(allIndexes, allSizes, h.start); + getNewOffset(allIndexes, allSizes, h.end); + getNewOffset(allIndexes, allSizes, h.handler); + h = h.next; + } + // updates the instructions addresses in the + // local var and line number tables + for (i = 0; i < 2; ++i) { + final ByteVector bv = i == 0 ? localVar : localVarType; + if (bv != null) { + b = bv.data; + u = 0; + while (u < bv.length) { + label = readUnsignedShort(b, u); + newOffset = getNewOffset(allIndexes, allSizes, 0, label); + writeShort(b, u, newOffset); + label += readUnsignedShort(b, u + 2); + newOffset = getNewOffset(allIndexes, allSizes, 0, label) + - newOffset; + writeShort(b, u + 2, newOffset); + u += 10; + } + } + } + if (lineNumber != null) { + b = lineNumber.data; + u = 0; + while (u < lineNumber.length) { + writeShort(b, u, getNewOffset(allIndexes, + allSizes, + 0, + readUnsignedShort(b, u))); + u += 4; + } + } + // updates the labels of the other attributes + Attribute attr = cattrs; + while (attr != null) { + final Label[] labels = attr.getLabels(); + if (labels != null) { + for (i = labels.length - 1; i >= 0; --i) { + getNewOffset(allIndexes, allSizes, labels[i]); + } + } + attr = attr.next; + } + + // replaces old bytecodes with new ones + code = newCode; + } + + /** + * Reads an unsigned short value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static int readUnsignedShort(final byte[] b, final int index) { + return (b[index] & 0xFF) << 8 | b[index + 1] & 0xFF; + } + + /** + * Reads a signed short value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static short readShort(final byte[] b, final int index) { + return (short) ((b[index] & 0xFF) << 8 | b[index + 1] & 0xFF); + } + + /** + * Reads a signed int value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static int readInt(final byte[] b, final int index) { + return (b[index] & 0xFF) << 24 | (b[index + 1] & 0xFF) << 16 + | (b[index + 2] & 0xFF) << 8 | b[index + 3] & 0xFF; + } + + /** + * Writes a short value in the given byte array. + * + * @param b a byte array. + * @param index where the first byte of the short value must be written. + * @param s the value to be written in the given byte array. + */ + static void writeShort(final byte[] b, final int index, final int s) { + b[index] = (byte) (s >>> 8); + b[index + 1] = (byte) s; + } + + /** + * Computes the future value of a bytecode offset.

Note: it is possible + * to have several entries for the same instruction in the indexes + * and sizes: two entries (index=a,size=b) and (index=a,size=b') + * are equivalent to a single entry (index=a,size=b+b'). + * + * @param indexes current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the first + * byte of the next instruction). + * @param sizes the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last |sizes[i]| + * bytes of the instruction will be removed (the instruction size + * must not become negative or null). + * @param begin index of the first byte of the source instruction. + * @param end index of the first byte of the target instruction. + * @return the future value of the given bytecode offset. + */ + static int getNewOffset( + final int[] indexes, + final int[] sizes, + final int begin, + final int end) { + int offset = end - begin; + for (int i = 0; i < indexes.length; ++i) { + if (begin < indexes[i] && indexes[i] <= end) { + // forward jump + offset += sizes[i]; + } else if (end < indexes[i] && indexes[i] <= begin) { + // backward jump + offset -= sizes[i]; + } + } + return offset; + } + + /** + * Updates the offset of the given label. + * + * @param indexes current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the first + * byte of the next instruction). + * @param sizes the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last |sizes[i]| + * bytes of the instruction will be removed (the instruction size + * must not become negative or null). + * @param label the label whose offset must be updated. + */ + static void getNewOffset( + final int[] indexes, + final int[] sizes, + final Label label) { + if ((label.status & Label.RESIZED) == 0) { + label.position = getNewOffset(indexes, sizes, 0, label.position); + label.status |= Label.RESIZED; + } + } +} diff --git a/src/org/rsbot/loader/asm/Opcodes.java b/src/org/rsbot/loader/asm/Opcodes.java new file mode 100644 index 0000000..f9272c1 --- /dev/null +++ b/src/org/rsbot/loader/asm/Opcodes.java @@ -0,0 +1,323 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +/** + * Defines the JVM opcodes, access flags and array type codes. This interface + * does not define all the JVM opcodes because some opcodes are automatically + * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced + * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n + * opcodes are therefore not defined in this interface. Likewise for LDC, + * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and + * JSR_W. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface Opcodes { + + // versions + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + + // access flags + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + + // ASM specific pseudo access flags + + int ACC_DEPRECATED = 0x20000; // class, field, method + + // types for NEWARRAY + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // stack map frame types + + /** + * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. + */ + int F_NEW = -1; + + /** + * Represents a compressed frame with complete frame data. + */ + int F_FULL = 0; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that additional 1-3 locals are defined, and + * with an empty stack. + */ + int F_APPEND = 1; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that the last 1-3 locals are absent and with + * an empty stack. + */ + int F_CHOP = 2; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with a single value on the stack. + */ + int F_SAME1 = 4; + + Integer TOP = new Integer(0); + Integer INTEGER = new Integer(1); + Integer FLOAT = new Integer(2); + Integer DOUBLE = new Integer(3); + Integer LONG = new Integer(4); + Integer NULL = new Integer(5); + Integer UNINITIALIZED_THIS = new Integer(6); + + /** + * Represents a owner of an invokedynamic call. + */ + String INVOKEDYNAMIC_OWNER = "java/lang/dyn/Dynamic"; + + // opcodes // visit method (- = idem) + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + // int LDC_W = 19; // - + // int LDC2_W = 20; // - + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + // int ILOAD_0 = 26; // - + // int ILOAD_1 = 27; // - + // int ILOAD_2 = 28; // - + // int ILOAD_3 = 29; // - + // int LLOAD_0 = 30; // - + // int LLOAD_1 = 31; // - + // int LLOAD_2 = 32; // - + // int LLOAD_3 = 33; // - + // int FLOAD_0 = 34; // - + // int FLOAD_1 = 35; // - + // int FLOAD_2 = 36; // - + // int FLOAD_3 = 37; // - + // int DLOAD_0 = 38; // - + // int DLOAD_1 = 39; // - + // int DLOAD_2 = 40; // - + // int DLOAD_3 = 41; // - + // int ALOAD_0 = 42; // - + // int ALOAD_1 = 43; // - + // int ALOAD_2 = 44; // - + // int ALOAD_3 = 45; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + // int ISTORE_0 = 59; // - + // int ISTORE_1 = 60; // - + // int ISTORE_2 = 61; // - + // int ISTORE_3 = 62; // - + // int LSTORE_0 = 63; // - + // int LSTORE_1 = 64; // - + // int LSTORE_2 = 65; // - + // int LSTORE_3 = 66; // - + // int FSTORE_0 = 67; // - + // int FSTORE_1 = 68; // - + // int FSTORE_2 = 69; // - + // int FSTORE_3 = 70; // - + // int DSTORE_0 = 71; // - + // int DSTORE_1 = 72; // - + // int DSTORE_2 = 73; // - + // int DSTORE_3 = 74; // - + // int ASTORE_0 = 75; // - + // int ASTORE_1 = 76; // - + // int ASTORE_2 = 77; // - + // int ASTORE_3 = 78; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // - + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + // int WIDE = 196; // NOT VISITED + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - + // int GOTO_W = 200; // - + // int JSR_W = 201; // - +} diff --git a/src/org/rsbot/loader/asm/Type.java b/src/org/rsbot/loader/asm/Type.java new file mode 100644 index 0000000..5fe493b --- /dev/null +++ b/src/org/rsbot/loader/asm/Type.java @@ -0,0 +1,750 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + */ +package org.rsbot.loader.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java type. This class can be used to make it easier to manipulate type and + * method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +@SuppressWarnings("rawtypes") +public class Type { + + /** + * The sort of the void type. See {@link #getSort getSort}. + */ + public static final int VOID = 0; + + /** + * The sort of the boolean type. See {@link #getSort getSort}. + */ + public static final int BOOLEAN = 1; + + /** + * The sort of the char type. See {@link #getSort getSort}. + */ + public static final int CHAR = 2; + + /** + * The sort of the byte type. See {@link #getSort getSort}. + */ + public static final int BYTE = 3; + + /** + * The sort of the short type. See {@link #getSort getSort}. + */ + public static final int SHORT = 4; + + /** + * The sort of the int type. See {@link #getSort getSort}. + */ + public static final int INT = 5; + + /** + * The sort of the float type. See {@link #getSort getSort}. + */ + public static final int FLOAT = 6; + + /** + * The sort of the long type. See {@link #getSort getSort}. + */ + public static final int LONG = 7; + + /** + * The sort of the double type. See {@link #getSort getSort}. + */ + public static final int DOUBLE = 8; + + /** + * The sort of array reference types. See {@link #getSort getSort}. + */ + public static final int ARRAY = 9; + + /** + * The sort of object reference type. See {@link #getSort getSort}. + */ + public static final int OBJECT = 10; + + /** + * The void type. + */ + public static final Type VOID_TYPE = new Type(VOID, null, 'V' << 24 + | 5 << 16 | 0 << 8 | 0, 1); + + /** + * The boolean type. + */ + public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, 'Z' << 24 + | 0 << 16 | 5 << 8 | 1, 1); + + /** + * The char type. + */ + public static final Type CHAR_TYPE = new Type(CHAR, null, 'C' << 24 + | 0 << 16 | 6 << 8 | 1, 1); + + /** + * The byte type. + */ + public static final Type BYTE_TYPE = new Type(BYTE, null, 'B' << 24 + | 0 << 16 | 5 << 8 | 1, 1); + + /** + * The short type. + */ + public static final Type SHORT_TYPE = new Type(SHORT, null, 'S' << 24 + | 0 << 16 | 7 << 8 | 1, 1); + + /** + * The int type. + */ + public static final Type INT_TYPE = new Type(INT, null, 'I' << 24 + | 0 << 16 | 0 << 8 | 1, 1); + + /** + * The float type. + */ + public static final Type FLOAT_TYPE = new Type(FLOAT, null, 'F' << 24 + | 2 << 16 | 2 << 8 | 1, 1); + + /** + * The long type. + */ + public static final Type LONG_TYPE = new Type(LONG, null, 'J' << 24 + | 1 << 16 | 1 << 8 | 2, 1); + + /** + * The double type. + */ + public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, 'D' << 24 + | 3 << 16 | 3 << 8 | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private final char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private final int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a reference type. + * + * @param sort the sort of the reference type to be constructed. + * @param buf a buffer containing the descriptor of the previous type. + * @param off the offset of this descriptor in the previous buffer. + * @param len the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param typeDescriptor a type descriptor. + * @return the Java type corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java type corresponding to the given internal name. + * + * @param internalName an internal name. + * @return the Java type corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + final char[] buf = internalName.toCharArray(); + return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); + } + + /** + * Returns the Java type corresponding to the given class. + * + * @param c a class. + * @return the Java type corresponding to the given class. + */ + public static Type getType(final Class c) { + if (c.isPrimitive()) { + if (c == Integer.TYPE) { + return INT_TYPE; + } else if (c == Void.TYPE) { + return VOID_TYPE; + } else if (c == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (c == Byte.TYPE) { + return BYTE_TYPE; + } else if (c == Character.TYPE) { + return CHAR_TYPE; + } else if (c == Short.TYPE) { + return SHORT_TYPE; + } else if (c == Double.TYPE) { + return DOUBLE_TYPE; + } else if (c == Float.TYPE) { + return FLOAT_TYPE; + } else /* if (c == Long.TYPE) */ { + return LONG_TYPE; + } + } else { + return getType(getDescriptor(c)); + } + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java types corresponding to the argument types of the given + * method descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + final char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + final char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + final Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method. + * + * @param method a method. + * @return the Java types corresponding to the argument types of the given + * method. + */ + public static Type[] getArgumentTypes(final Method method) { + final Class[] classes = method.getParameterTypes(); + final Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java type corresponding to the return type of the given + * method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + final char[] buf = methodDescriptor.toCharArray(); + return getType(buf, methodDescriptor.indexOf(')') + 1); + } + + /** + * Returns the Java type corresponding to the return type of the given + * method. + * + * @param method a method. + * @return the Java type corresponding to the return type of the given + * method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param desc the descriptor of a method. + * @return the size of the arguments of the method (plus one for the + * implicit this argument), argSize, and the size of its return + * value, retSize, packed into a single int i = + * (argSize << 2) | retSize (argSize is therefore equal + * to i >> 2, and retSize to i & 0x03). + */ + public static int getArgumentsAndReturnSizes(final String desc) { + int n = 1; + int c = 1; + while (true) { + char car = desc.charAt(c++); + if (car == ')') { + car = desc.charAt(c); + return n << 2 + | (car == 'V' ? 0 : car == 'D' || car == 'J' ? 2 : 1); + } else if (car == 'L') { + while (desc.charAt(c++) != ';') { + } + n += 1; + } else if (car == '[') { + while ((car = desc.charAt(c)) == '[') { + ++c; + } + if (car == 'D' || car == 'J') { + n -= 1; + } + } else if (car == 'D' || car == 'J') { + n += 2; + } else { + n += 1; + } + } + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param buf a buffer containing a type descriptor. + * @param off the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + // case 'L': + default: + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the sort of this Java type. + * + * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, + * {@link #CHAR CHAR}, {@link #BYTE BYTE}, {@link #SHORT SHORT}, + * {@link #INT INT}, {@link #FLOAT FLOAT}, {@link #LONG LONG}, + * {@link #DOUBLE DOUBLE}, {@link #ARRAY ARRAY} or + * {@link #OBJECT OBJECT}. + */ + public int getSort() { + return sort; + } + + /** + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements of this array type. This method should + * only be used for an array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the name of the class corresponding to this type. + * + * @return the fully qualified name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + final StringBuffer b = new StringBuffer(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + b.append("[]"); + } + return b.toString(); + // case OBJECT: + default: + return new String(buf, off, len).replace('/', '.'); + } + } + + /** + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return new String(buf, off, len); + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /** + * Returns the descriptor corresponding to this Java type. + * + * @return the descriptor corresponding to this Java type. + */ + public String getDescriptor() { + final StringBuffer buf = new StringBuffer(); + getDescriptor(buf); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return + * types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return + * types. + */ + public static String getMethodDescriptor( + final Type returnType, + final Type[] argumentTypes) { + final StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].getDescriptor(buf); + } + buf.append(')'); + returnType.getDescriptor(buf); + return buf.toString(); + } + + /** + * Appends the descriptor corresponding to this Java type to the given + * string buffer. + * + * @param buf the string buffer to which the descriptor must be appended. + */ + private void getDescriptor(final StringBuffer buf) { + if (this.buf == null) { + // descriptor is in byte 3 of 'off' for primitive types (buf == null) + buf.append((char) ((off & 0xFF000000) >>> 24)); + } else if (sort == ARRAY) { + buf.append(this.buf, off, len); + } else { // sort == OBJECT + buf.append('L'); + buf.append(this.buf, off, len); + buf.append(';'); + } + } + + // ------------------------------------------------------------------------ + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ------------------------------------------------------------------------ + + /** + * Returns the internal name of the given class. The internal name of a + * class is its fully qualified name, as returned by Class.getName(), where + * '.' are replaced by '/'. + * + * @param c an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class c) { + return c.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given Java type. + * + * @param c an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class c) { + final StringBuffer buf = new StringBuffer(); + getDescriptor(buf, c); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param c a {@link Constructor Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor c) { + final Class[] parameters = c.getParameterTypes(); + final StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + return buf.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param m a {@link Method Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method m) { + final Class[] parameters = m.getParameterTypes(); + final StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + buf.append(')'); + getDescriptor(buf, m.getReturnType()); + return buf.toString(); + } + + /** + * Appends the descriptor of the given class to the given string buffer. + * + * @param buf the string buffer to which the descriptor must be appended. + * @param c the class whose descriptor must be computed. + */ + private static void getDescriptor(final StringBuffer buf, final Class c) { + Class d = c; + while (true) { + if (d.isPrimitive()) { + char car; + if (d == Integer.TYPE) { + car = 'I'; + } else if (d == Void.TYPE) { + car = 'V'; + } else if (d == Boolean.TYPE) { + car = 'Z'; + } else if (d == Byte.TYPE) { + car = 'B'; + } else if (d == Character.TYPE) { + car = 'C'; + } else if (d == Short.TYPE) { + car = 'S'; + } else if (d == Double.TYPE) { + car = 'D'; + } else if (d == Float.TYPE) { + car = 'F'; + } else /* if (d == Long.TYPE) */ { + car = 'J'; + } + buf.append(car); + return; + } else if (d.isArray()) { + buf.append('['); + d = d.getComponentType(); + } else { + buf.append('L'); + final String name = d.getName(); + final int len = name.length(); + for (int i = 0; i < len; ++i) { + final char car = name.charAt(i); + buf.append(car == '.' ? '/' : car); + } + buf.append(';'); + return; + } + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /** + * Returns the size of values of this type. + * + * @return the size of values of this type, i.e., 2 for long and + * double, 0 for void and 1 otherwise. + */ + public int getSize() { + // the size is in byte 0 of 'off' for primitive types (buf == null) + return buf == null ? off & 0xFF : 1; + } + + /** + * Returns a JVM instruction opcode adapted to this Java type. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, + * ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is float and + * opcode is IRETURN, this method returns FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4); + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /** + * Tests if the given object is equal to this type. + * + * @param o the object to be compared to this type. + * @return true if the given object is equal to this type. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Type)) { + return false; + } + final Type t = (Type) o; + if (sort != t.sort) { + return false; + } + if (sort == OBJECT || sort == ARRAY) { + if (len != t.len) { + return false; + } + for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { + if (buf[i] != t.buf[j]) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hc = 13 * sort; + if (sort == OBJECT || sort == ARRAY) { + for (int i = off, end = i + len; i < end; i++) { + hc = 17 * (hc + buf[i]); + } + } + return hc; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/src/org/rsbot/loader/script/Buffer.java b/src/org/rsbot/loader/script/Buffer.java new file mode 100644 index 0000000..faca0eb --- /dev/null +++ b/src/org/rsbot/loader/script/Buffer.java @@ -0,0 +1,58 @@ +package org.rsbot.loader.script; + +/** + * @author Jacmob + */ +class Buffer { + + private int pos; + private final byte[] data; + + public Buffer(final byte[] buffer) { + data = buffer; + pos = 0; + } + + public int g1() { + return data[pos++] & 0xff; + } + + public int g2() { + pos += 2; + return ((data[pos - 2] & 0xff) << 8) + (data[pos - 1] & 0xff); + } + + public int g4() { + pos += 4; + return ((data[pos - 4] & 0xff) << 24) + ((data[pos - 3] & 0xff) << 16) + ((data[pos - 2] & 0xff) << 8) + (data[pos - 1] & 0xff); + } + + public long g8() { + final long l = g4() & 0xffffffffL; + final long r = g4() & 0xffffffffL; + return (l << 32) + r; + } + + public String gstr() { + final int i = pos; + while (data[pos++] != 10) { + } + return new String(data, i, pos - i - 1); + } + + public byte[] gstrbyte() { + final int i = pos; + while (data[pos++] != 10) { + } + final byte str[] = new byte[pos - i - 1]; + System.arraycopy(data, i, str, i - i, pos - 1 - i); + return str; + } + + public void gdata(final byte[] data, final int len, final int off) { + for (int i = off; i < off + len; i++) { + data[i] = this.data[pos++]; + } + } + +} diff --git a/src/org/rsbot/loader/script/CodeReader.java b/src/org/rsbot/loader/script/CodeReader.java new file mode 100644 index 0000000..16fce75 --- /dev/null +++ b/src/org/rsbot/loader/script/CodeReader.java @@ -0,0 +1,110 @@ +package org.rsbot.loader.script; + +import org.rsbot.loader.asm.Label; +import org.rsbot.loader.asm.MethodVisitor; + +/** + * @author Jacmob + */ +public class CodeReader { + + static interface Opcodes { + int INSN = 1; + int INT_INSN = 2; + int VAR_INSN = 3; + int TYPE_INSN = 4; + int FIELD_INSN = 5; + int METHOD_INSN = 6; + int JUMP_INSN = 7; + int LDC_INSN = 8; + int IINC_INSN = 9; + int TABLESWITCH_INSN = 10; + int LOOKUPSWITCH_INSN = 11; + int MULTIANEWARRAY_INSN = 12; + int TRY_CATCH_BLOCK = 13; + int LOCAL_VARIABLE = 14; + int LABEL = 15; + } + + private final Buffer code; + + public CodeReader(final byte[] code) { + this.code = new Buffer(code); + } + + public void accept(final MethodVisitor v) { + int len = code.g2(); + final Label[] labels = new Label[code.g1()]; + for (int i = 0, l = labels.length; i < l; ++i) { + labels[i] = new Label(); + } + while (len-- > 0) { + final int op = code.g1(); + if (op == Opcodes.INSN) { + v.visitInsn(code.g1()); + } else if (op == Opcodes.INT_INSN) { + v.visitIntInsn(code.g1(), code.g2()); + } else if (op == Opcodes.VAR_INSN) { + v.visitVarInsn(code.g1(), code.g1()); + } else if (op == Opcodes.TYPE_INSN) { + v.visitTypeInsn(code.g1(), code.gstr()); + } else if (op == Opcodes.FIELD_INSN) { + v.visitFieldInsn(code.g1(), code.gstr(), code.gstr(), code.gstr()); + } else if (op == Opcodes.METHOD_INSN) { + v.visitMethodInsn(code.g1(), code.gstr(), code.gstr(), code.gstr()); + } else if (op == Opcodes.JUMP_INSN) { + v.visitJumpInsn(code.g1(), labels[code.g1()]); + } else if (op == Opcodes.LDC_INSN) { + final int type = code.g1(); + if (type == 1) { + v.visitLdcInsn(code.g4()); + } else if (type == 2) { + v.visitLdcInsn(Float.parseFloat(code.gstr())); + } else if (type == 3) { + v.visitLdcInsn(code.g8()); + } else if (type == 4) { + v.visitLdcInsn(Double.parseDouble(code.gstr())); + } else if (type == 5) { + v.visitLdcInsn(code.gstr()); + } + } else if (op == Opcodes.IINC_INSN) { + v.visitIincInsn(code.g1(), code.g1()); + } else if (op == Opcodes.TABLESWITCH_INSN) { + final int min = code.g2(); + final int max = code.g2(); + final Label dflt = labels[code.g1()]; + final int n = code.g1(); + int ptr = 0; + final Label[] lbls = new Label[n]; + while (ptr < n) { + lbls[ptr++] = labels[code.g1()]; + } + v.visitTableSwitchInsn(min, max, dflt, lbls); + } else if (op == Opcodes.LOOKUPSWITCH_INSN) { + final Label dflt = labels[code.g1()]; + int n = code.g1(), ptr = 0; + final int[] keys = new int[n]; + while (ptr < n) { + keys[ptr++] = code.g2(); + } + n = code.g1(); + ptr = 0; + final Label[] lbls = new Label[n]; + while (ptr < n) { + lbls[ptr++] = labels[code.g1()]; + } + v.visitLookupSwitchInsn(dflt, keys, lbls); + } else if (op == Opcodes.MULTIANEWARRAY_INSN) { + v.visitMultiANewArrayInsn(code.gstr(), code.g1()); + } else if (op == Opcodes.TRY_CATCH_BLOCK) { + v.visitTryCatchBlock(labels[code.g1()], labels[code.g1()], labels[code.g1()], code.gstr()); + } else if (op == Opcodes.LOCAL_VARIABLE) { + v.visitLocalVariable(code.gstr(), code.gstr(), code.gstr(), labels[code.g1()], labels[code.g1()], + code.g1()); + } else if (op == Opcodes.LABEL) { + v.visitLabel(labels[code.g1()]); + } + } + } + +} diff --git a/src/org/rsbot/loader/script/ModScript.java b/src/org/rsbot/loader/script/ModScript.java new file mode 100644 index 0000000..7d2084e --- /dev/null +++ b/src/org/rsbot/loader/script/ModScript.java @@ -0,0 +1,207 @@ +package org.rsbot.loader.script; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassReader; +import org.rsbot.loader.asm.ClassVisitor; +import org.rsbot.loader.asm.ClassWriter; +import org.rsbot.loader.script.adapter.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Jacmob + */ +public class ModScript { + + public static interface Opcodes { + int ATTRIBUTE = 1; + int GET_STATIC = 2; + int GET_FIELD = 3; + int ADD_FIELD = 4; + int ADD_METHOD = 5; + int ADD_INTERFACE = 6; + int SET_SUPER = 7; + int SET_SIGNATURE = 8; + int INSERT_CODE = 9; + int OVERRIDE_CLASS = 10; + } + + public static final int MAGIC = 0xFADFAD; + + private String name; + private int version; + private Map attributes; + private Map adapters; + private Map writers; + + public ModScript(final byte[] data) throws ParseException { + load(new Buffer(data)); + } + + public String getName() { + return name; + } + + public int getVersion() { + return version; + } + + public String getAttribute(final String key) { + return attributes.get(key); + } + + public byte[] process(final String key, final byte[] data) { + final ClassAdapter adapter = adapters.get(key); + if (adapter != null) { + final ClassReader reader = new ClassReader(data); + reader.accept(adapter, ClassReader.SKIP_FRAMES); + return writers.get(key).toByteArray(); + } + return data; + } + + public byte[] process(final String key, final InputStream is) throws IOException { + final ClassAdapter adapter = adapters.get(key); + if (adapter != null) { + final ClassReader reader = new ClassReader(is); + reader.accept(adapter, ClassReader.SKIP_FRAMES); + return writers.get(key).toByteArray(); + } + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + final byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) != -1) { + os.write(buffer, 0, n); + } + return os.toByteArray(); + } + + private void load(final Buffer buff) throws ParseException { + if (buff.g4() != ModScript.MAGIC) { + throw new ParseException("Bad magic!"); + } + attributes = new HashMap(); + adapters = new HashMap(); + writers = new HashMap(); + name = buff.gstr(); + version = buff.g2(); + int num = buff.g2(); + while (num-- > 0) { + final int op = buff.g1(); + if (op == Opcodes.ATTRIBUTE) { + final String key = buff.gstr(); + final String value = buff.gstr(); + attributes.put(key, new StringBuilder(value).reverse().toString()); + } else if (op == Opcodes.GET_STATIC || op == Opcodes.GET_FIELD) { + final String clazz = buff.gstr(); + final int count = buff.g2(); + int ptr = 0; + final AddGetterAdapter.Field[] fields = new AddGetterAdapter.Field[count]; + while (ptr < count) { + final AddGetterAdapter.Field f = new AddGetterAdapter.Field(); + f.getter_access = buff.g4(); + f.getter_name = buff.gstr(); + f.getter_desc = buff.gstr(); + f.owner = buff.gstr(); + f.name = buff.gstr(); + f.desc = buff.gstr(); + + fields[ptr++] = f; + } + adapters.put(clazz, new AddGetterAdapter(delegate(clazz), op == Opcodes.GET_FIELD, fields)); + } else if (op == Opcodes.ADD_FIELD) { + final String clazz = buff.gstr(); + final int count = buff.g2(); + int ptr = 0; + final AddFieldAdapter.Field[] fields = new AddFieldAdapter.Field[count]; + while (ptr < count) { + final AddFieldAdapter.Field f = new AddFieldAdapter.Field(); + f.access = buff.g4(); + f.name = buff.gstr(); + f.desc = buff.gstr(); + fields[ptr++] = f; + } + adapters.put(clazz, new AddFieldAdapter(delegate(clazz), fields)); + } else if (op == Opcodes.ADD_METHOD) { + final String clazz = buff.gstr(); + final int count = buff.g2(); + int ptr = 0; + final AddMethodAdapter.Method[] methods = new AddMethodAdapter.Method[count]; + while (ptr < count) { + final AddMethodAdapter.Method m = new AddMethodAdapter.Method(); + m.access = buff.g4(); + m.name = buff.gstr(); + m.desc = buff.gstr(); + final byte[] code = new byte[buff.g4()]; + buff.gdata(code, code.length, 0); + m.code = code; + m.max_locals = buff.g1(); + m.max_stack = buff.g1(); + methods[ptr++] = m; + } + adapters.put(clazz, new AddMethodAdapter(delegate(clazz), methods)); + } else if (op == Opcodes.ADD_INTERFACE) { + final String clazz = buff.gstr(); + final String inter = buff.gstr(); + adapters.put(clazz, new AddInterfaceAdapter(delegate(clazz), inter)); + } else if (op == Opcodes.SET_SUPER) { + final String clazz = buff.gstr(); + final String superName = buff.gstr(); + adapters.put(clazz, new SetSuperAdapter(delegate(clazz), superName)); + } else if (op == Opcodes.SET_SIGNATURE) { + final String clazz = buff.gstr(); + final int count = buff.g2(); + int ptr = 0; + final SetSignatureAdapter.Signature[] signatures = new SetSignatureAdapter.Signature[count]; + while (ptr < count) { + final SetSignatureAdapter.Signature s = new SetSignatureAdapter.Signature(); + s.name = buff.gstr(); + s.desc = buff.gstr(); + s.new_access = buff.g4(); + s.new_name = buff.gstr(); + s.new_desc = buff.gstr(); + signatures[ptr++] = s; + } + adapters.put(clazz, new SetSignatureAdapter(delegate(clazz), signatures)); + } else if (op == Opcodes.INSERT_CODE) { + final String clazz = buff.gstr(); + final String name = buff.gstr(); + final String desc = buff.gstr(); + int count = buff.g1(); + final Map fragments = new HashMap(); + while (count-- > 0) { + final int off = buff.g2(); + final byte[] code = new byte[buff.g4()]; + buff.gdata(code, code.length, 0); + fragments.put(off, code); + } + adapters.put(clazz, new InsertCodeAdapter(delegate(clazz), + name, desc, fragments, buff.g1(), buff.g1())); + } else if (op == Opcodes.OVERRIDE_CLASS) { + final String old_clazz = buff.gstr(); + final String new_clazz = buff.gstr(); + int count = buff.g1(); + while (count-- > 0) { + final String clazz = buff.gstr(); + adapters.put(clazz, new OverrideClassAdapter(delegate(clazz), old_clazz, new_clazz)); + } + } + } + } + + private ClassVisitor delegate(final String clazz) { + final ClassAdapter delegate = adapters.get(clazz); + if (delegate == null) { + final ClassWriter writer = new ClassWriter(0); + writers.put(clazz, writer); + return writer; + } else { + return delegate; + } + } + +} \ No newline at end of file diff --git a/src/org/rsbot/loader/script/ParseException.java b/src/org/rsbot/loader/script/ParseException.java new file mode 100644 index 0000000..28e7b74 --- /dev/null +++ b/src/org/rsbot/loader/script/ParseException.java @@ -0,0 +1,17 @@ +package org.rsbot.loader.script; + +/** + * @author Jacmob + */ +public class ParseException extends Exception { + + /** + * Exception Message + */ + private static final long serialVersionUID = -7099026388712137749L; + + public ParseException(final String message) { + super(message); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/AddFieldAdapter.java b/src/org/rsbot/loader/script/adapter/AddFieldAdapter.java new file mode 100644 index 0000000..ab91bc0 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/AddFieldAdapter.java @@ -0,0 +1,32 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassVisitor; + +/** + * @author Jacmob + */ +public class AddFieldAdapter extends ClassAdapter { + + public static class Field { + public int access; + public String name; + public String desc; + } + + private final Field[] fields; + + public AddFieldAdapter(final ClassVisitor delegate, final Field[] fields) { + super(delegate); + this.fields = fields; + } + + @Override + public void visitEnd() { + for (final Field f : fields) { + cv.visitField(f.access, f.name, f.desc, null, null); + } + cv.visitEnd(); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/AddGetterAdapter.java b/src/org/rsbot/loader/script/adapter/AddGetterAdapter.java new file mode 100644 index 0000000..e994a3e --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/AddGetterAdapter.java @@ -0,0 +1,114 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassVisitor; +import org.rsbot.loader.asm.MethodVisitor; +import org.rsbot.loader.asm.Opcodes; + +/** + * @author Jacmob + */ +public class AddGetterAdapter extends ClassAdapter implements Opcodes { + + public static class Field { + public int getter_access; + public String getter_name; + public String getter_desc; + public String owner; + public String name; + public String desc; + } + + private final boolean virtual; + private final Field[] fields; + + private String owner; + + public AddGetterAdapter(final ClassVisitor delegate, final boolean virtual, final Field[] fields) { + super(delegate); + this.virtual = virtual; + this.fields = fields; + } + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + owner = name; + cv.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitEnd() { + if (virtual) { + for (final Field f : fields) { + visitGetter(f.getter_access, f.getter_name, f.getter_desc, f.name, f.desc); + } + } else { + for (final Field f : fields) { + visitGetter(f.getter_access, f.getter_name, f.getter_desc, f.owner, f.name, f.desc); + } + } + cv.visitEnd(); + } + + private void visitGetter( + final int getter_access, + final String getter_name, + final String getter_desc, + final String name, + final String desc) { + final MethodVisitor mv = cv.visitMethod(getter_access, getter_name, getter_desc, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, owner, name, desc); + final int op = getReturnOpcode(desc); + mv.visitInsn(op); + mv.visitMaxs(op == LRETURN || op == DRETURN ? 2 : 1, (getter_access & ACC_STATIC) == 0 ? 1 : 0); + mv.visitEnd(); + } + + private void visitGetter( + final int getter_access, + final String getter_name, + final String getter_desc, + final String owner, + final String name, + final String desc) { + final MethodVisitor mv = cv.visitMethod(getter_access, getter_name, getter_desc, null, null); + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, owner, name, desc); + final int op = getReturnOpcode(desc); + mv.visitInsn(op); + mv.visitMaxs(op == LRETURN || op == DRETURN ? 2 : 1, (getter_access & ACC_STATIC) == 0 ? 1 : 0); + mv.visitEnd(); + } + + private int getReturnOpcode(String desc) { + desc = desc.substring(desc.indexOf(")") + 1); + if (desc.length() > 1) { + return ARETURN; + } + final char c = desc.charAt(0); + switch (c) { + case 'I': + case 'Z': + case 'B': + case 'S': + case 'C': + return IRETURN; + case 'J': + return LRETURN; + case 'F': + return FRETURN; + case 'D': + return DRETURN; + } + throw new RuntimeException("eek"); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/AddInterfaceAdapter.java b/src/org/rsbot/loader/script/adapter/AddInterfaceAdapter.java new file mode 100644 index 0000000..1cfe052 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/AddInterfaceAdapter.java @@ -0,0 +1,32 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassVisitor; + +/** + * @author Jacmob + */ +public class AddInterfaceAdapter extends ClassAdapter { + + private final String inter; + + public AddInterfaceAdapter(final ClassVisitor delegate, final String inter) { + super(delegate); + this.inter = inter; + } + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + final String[] inters = new String[interfaces.length + 1]; + System.arraycopy(interfaces, 0, inters, 0, interfaces.length); + inters[interfaces.length] = inter; + cv.visit(version, access, name, signature, superName, inters); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/AddMethodAdapter.java b/src/org/rsbot/loader/script/adapter/AddMethodAdapter.java new file mode 100644 index 0000000..b0290ff --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/AddMethodAdapter.java @@ -0,0 +1,41 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassVisitor; +import org.rsbot.loader.asm.MethodVisitor; +import org.rsbot.loader.script.CodeReader; + +/** + * @author Jacmob + */ +public class AddMethodAdapter extends ClassAdapter { + + public static class Method { + public int access; + public String name; + public String desc; + public byte[] code; + public int max_stack; + public int max_locals; + } + + private final Method[] methods; + + public AddMethodAdapter(final ClassVisitor delegate, final Method[] methods) { + super(delegate); + this.methods = methods; + } + + @Override + public void visitEnd() { + for (final Method m : methods) { + final MethodVisitor mv = cv.visitMethod(m.access, m.name, m.desc, null, null); + mv.visitCode(); + new CodeReader(m.code).accept(mv); + mv.visitMaxs(m.max_stack, m.max_locals); + mv.visitEnd(); + } + cv.visitEnd(); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/InsertCodeAdapter.java b/src/org/rsbot/loader/script/adapter/InsertCodeAdapter.java new file mode 100644 index 0000000..beb11a0 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/InsertCodeAdapter.java @@ -0,0 +1,213 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.*; +import org.rsbot.loader.script.CodeReader; + +import java.util.Map; + +/** + * @author Jacmob + */ +public class InsertCodeAdapter extends ClassAdapter { + + private final String method_name; + private final String method_desc; + private final Map fragments; + private final int max_locals; + private final int max_stack; + + public InsertCodeAdapter( + final ClassVisitor delegate, + final String method_name, + final String method_desc, + final Map fragments, + final int max_locals, + final int max_stack) { + super(delegate); + this.method_name = method_name; + this.method_desc = method_desc; + this.fragments = fragments; + this.max_locals = max_locals; + this.max_stack = max_stack; + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + final MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + if (name.equals(method_name) && desc.equals(method_desc)) { + return new MethodAdapter(mv, fragments, max_locals, max_stack); + } + return mv; + } + + static class MethodAdapter implements MethodVisitor { + + private final MethodVisitor mv; + private final Map fragments; + private final int max_locals; + private final int max_stack; + + private int idx = 0; + + MethodAdapter( + final MethodVisitor delegate, + final Map fragments, + final int max_locals, + final int max_stack) { + mv = delegate; + this.fragments = fragments; + this.max_locals = max_locals; + this.max_stack = max_stack; + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return mv.visitAnnotationDefault(); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + return mv.visitAnnotation(desc, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + + @Override + public void visitAttribute(final Attribute attr) { + mv.visitAttribute(attr); + } + + @Override + public void visitCode() { + mv.visitCode(); + } + + @Override + public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { + + } + + @Override + public void visitInsn(final int opcode) { + checkFragments(); + mv.visitInsn(opcode); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + checkFragments(); + mv.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + checkFragments(); + mv.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + checkFragments(); + mv.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + checkFragments(); + mv.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + checkFragments(); + mv.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + checkFragments(); + mv.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(final Label label) { + checkFragments(); + mv.visitLabel(label); + } + + @Override + public void visitLdcInsn(final Object cst) { + checkFragments(); + mv.visitLdcInsn(cst); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + checkFragments(); + mv.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) { + checkFragments(); + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + checkFragments(); + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + checkFragments(); + mv.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { + mv.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (max_stack == -1) { + mv.visitMaxs(maxStack, maxLocals); + } else { + mv.visitMaxs(max_stack, max_locals); + } + } + + @Override + public void visitEnd() { + mv.visitEnd(); + } + + private void checkFragments() { + if (fragments.containsKey(++idx)) { + new CodeReader(fragments.get(idx)).accept(mv); + } + } + + } + +} diff --git a/src/org/rsbot/loader/script/adapter/OverrideClassAdapter.java b/src/org/rsbot/loader/script/adapter/OverrideClassAdapter.java new file mode 100644 index 0000000..594eaf6 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/OverrideClassAdapter.java @@ -0,0 +1,187 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.*; + +/** + * @author Liang + */ +public class OverrideClassAdapter extends ClassAdapter { + + private final String old_clazz; + private final String new_clazz; + + public OverrideClassAdapter(final ClassVisitor delegate, final String old_clazz, final String new_clazz) { + super(delegate); + this.old_clazz = old_clazz; + this.new_clazz = new_clazz; + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + return new MethodAdapter(cv.visitMethod(access, name, desc, signature, exceptions), old_clazz, new_clazz); + } + + @Override + public FieldVisitor visitField(final int access, final String name, String desc, final String signature, final Object value) { + if (desc.equals("L" + old_clazz + ";")) { + desc = "L" + new_clazz + ";"; + } + + return cv.visitField(access, name, desc, signature, value); + } + + static class MethodAdapter implements MethodVisitor { + + private final MethodVisitor mv; + private final String old_clazz; + private final String new_clazz; + + MethodAdapter( + final MethodVisitor delegate, + final String old_clazz, + final String new_clazz) { + mv = delegate; + this.old_clazz = old_clazz; + this.new_clazz = new_clazz; + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return mv.visitAnnotationDefault(); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + return mv.visitAnnotation(desc, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + + @Override + public void visitAttribute(final Attribute attr) { + mv.visitAttribute(attr); + } + + @Override + public void visitCode() { + mv.visitCode(); + } + + @Override + public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { + + } + + @Override + public void visitInsn(final int opcode) { + mv.visitInsn(opcode); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + mv.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + mv.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(final int opcode, String type) { + if (type.equals(old_clazz)) { + type = new_clazz; + } + + mv.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, String desc) { + if (desc.contains(old_clazz)) { + desc = desc.replace("L" + old_clazz + ";", "L" + new_clazz + ";"); + } + + mv.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(final int opcode, String owner, final String name, String desc) { + if (owner.equals(old_clazz)) { + owner = new_clazz; + desc = desc.replace("L" + old_clazz + ";", "L" + new_clazz + ";"); + } + mv.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + mv.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(final Label label) { + mv.visitLabel(label); + } + + @Override + public void visitLdcInsn(final Object cst) { + mv.visitLdcInsn(cst); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + mv.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + mv.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { + mv.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + mv.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + mv.visitEnd(); + } + + } + +} diff --git a/src/org/rsbot/loader/script/adapter/SetSignatureAdapter.java b/src/org/rsbot/loader/script/adapter/SetSignatureAdapter.java new file mode 100644 index 0000000..98a9ab1 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/SetSignatureAdapter.java @@ -0,0 +1,47 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.ClassAdapter; +import org.rsbot.loader.asm.ClassVisitor; +import org.rsbot.loader.asm.MethodVisitor; + +/** + * @author Jacmob + */ +public class SetSignatureAdapter extends ClassAdapter { + + public static class Signature { + public String name; + public String desc; + public int new_access; + public String new_name; + public String new_desc; + } + + private final Signature[] signatures; + + public SetSignatureAdapter(final ClassVisitor delegate, final Signature[] signatures) { + super(delegate); + this.signatures = signatures; + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + for (final Signature s : signatures) { + if (s.name.equals(name) && s.desc.equals("") || s.desc.equals(desc)) { + return cv.visitMethod( + s.new_access == -1 ? access : s.new_access, + s.new_name, + s.new_desc.equals("") ? desc : s.new_desc, + signature, + exceptions); + } + } + return cv.visitMethod(access, name, desc, signature, exceptions); + } + +} diff --git a/src/org/rsbot/loader/script/adapter/SetSuperAdapter.java b/src/org/rsbot/loader/script/adapter/SetSuperAdapter.java new file mode 100644 index 0000000..5afab68 --- /dev/null +++ b/src/org/rsbot/loader/script/adapter/SetSuperAdapter.java @@ -0,0 +1,51 @@ +package org.rsbot.loader.script.adapter; + +import org.rsbot.loader.asm.*; + +/** + * @author Jacmob + */ +public class SetSuperAdapter extends ClassAdapter { + + private String superName; + private final String newSuperName; + + public SetSuperAdapter(final ClassVisitor delegate, final String superName) { + super(delegate); + newSuperName = superName; + } + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + this.superName = superName; + cv.visit(version, access, name, signature, newSuperName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + if (name.equals("")) { + return new MethodAdapter(cv.visitMethod(access, name, desc, signature, exceptions)) { + @Override + public void visitMethodInsn(final int opcode, String owner, final String name, final String desc) { + if (opcode == Opcodes.INVOKESPECIAL && owner.equals(superName)) { + owner = newSuperName; + } + mv.visitMethodInsn(opcode, owner, name, desc); + } + }; + } + return cv.visitMethod(access, name, desc, signature, exceptions); + } + +} diff --git a/src/org/rsbot/log/LogFormatter.java b/src/org/rsbot/log/LogFormatter.java new file mode 100644 index 0000000..9d41ccd --- /dev/null +++ b/src/org/rsbot/log/LogFormatter.java @@ -0,0 +1,61 @@ +package org.rsbot.log; + +import org.rsbot.util.StringUtil; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class LogFormatter extends Formatter { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private final boolean appendNewLine; + + public LogFormatter() { + this(true); + } + + public LogFormatter(final boolean appendNewLine) { + this.appendNewLine = appendNewLine; + } + + @Override + public String format(final LogRecord record) { + final StringBuilder result = new StringBuilder().append("[").append(record.getLevel().getName()).append("] "). + append(new Date(record.getMillis())).append(": ").append(record.getLoggerName()).append(": "). + append(record.getMessage()).append(StringUtil.throwableToString(record.getThrown())); + if (appendNewLine) { + result.append(LogFormatter.LINE_SEPARATOR); + } + return result.toString(); + } + + @Override + public String formatMessage(final LogRecord record) { + return String.format(record.getMessage()); + } + + public String formatTimestamp(final LogRecord record) { + final SimpleDateFormat dateFormat = new SimpleDateFormat("hh:mm:ss"); + return "[" + dateFormat.format(record.getMillis()) + "]"; + } + + public String formatClass(final LogRecord record) { + final String append = "..."; + final String[] className = record.getLoggerName().split("\\."); + final String name = className[className.length - 1]; + final int maxLen = 16; + + return String.format( + name.length() > maxLen ? name.substring(0, + maxLen - append.length()) + + append : name); + } + + public String formatError(final LogRecord record) { + return StringUtil.throwableToString(record.getThrown()); + } + +} \ No newline at end of file diff --git a/src/org/rsbot/log/LogOutputStream.java b/src/org/rsbot/log/LogOutputStream.java new file mode 100644 index 0000000..0a90402 --- /dev/null +++ b/src/org/rsbot/log/LogOutputStream.java @@ -0,0 +1,153 @@ +package org.rsbot.log; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/* + * Copyright 1999-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +public class LogOutputStream extends OutputStream { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + /** + * Used to maintain the contract of {@link #close()}. + */ + protected boolean hasBeenClosed = false; + + /** + * The internal buffer where data is stored. + */ + + protected ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_LENGTH); + + /** + * The default number of bytes in the buffer. =2048 + */ + public static final int DEFAULT_BUFFER_LENGTH = 2048; + + /** + * The category to write to. + */ + protected Logger category; + + /** + * The priority to use when writing to the Category. + */ + protected Level priority; + + /** + * Creates the LogOutputStream to flush to the given Category. + * + * @param cat the Category to write to + * @param priority the Priority to use when writing to the Category + * @throws IllegalArgumentException if cat == null or priority == null + */ + public LogOutputStream(final Logger cat, final Level priority) throws IllegalArgumentException { + if (cat == null) { + throw new IllegalArgumentException("cat == null"); + } + if (priority == null) { + throw new IllegalArgumentException("priority == null"); + } + + this.priority = priority; + category = cat; + } + + /** + * Closes this output stream and releases any system resources associated + * with this stream. The general contract of close is that it + * closes the output stream. A closed stream cannot perform output + * operations and cannot be reopened. + */ + + @Override + public void close() { + flush(); + hasBeenClosed = true; + } + + /** + * Flushes this output stream and forces any buffered output bytes to be + * written out. The general contract of flush is that calling + * it is an indication that, if any bytes previously written have been + * buffered by the implementation of the output stream, such bytes should + * immediately be written to their intended destination. + */ + @Override + public void flush() { + final int pos = buffer.position(); + if (pos == 0) { + return; + } + // don't print out blank lines; flushing from PrintStream puts out these + if (pos == LogOutputStream.LINE_SEPARATOR.length()) { + + if ((char) buffer.get(0) == LogOutputStream.LINE_SEPARATOR.charAt(0) && (pos == 1 || // <- + // Unix + // & + // Mac, + // -> + // Windows + pos == 2 && (char) buffer.get(1) == LogOutputStream.LINE_SEPARATOR.charAt(1))) { + reset(); + return; + } + } + + category.log(priority, new String(buffer.array())); + reset(); + } + + private void reset() { + buffer.clear(); + } + + /** + * Writes the specified byte to this output stream. The general contract for + * write is that one byte is written to the output stream. The + * byte to be written is the eight low-order bits of the argument + * b. The 24 high-order bits of b are ignored. + * + * @param b the byte to write + * @throws IOException if an I/O error occurs. In particular, an + * IOException may be thrown if the output stream + * has been closed. + */ + @Override + public void write(final int b) throws IOException { + if (hasBeenClosed) { + throw new IOException("The stream has been closed."); + } + + // don't log nulls + if (b == 0) { + return; + } + + if (buffer.position() >= buffer.capacity()) { + //make bigger + final ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2); + newBuffer.put(buffer); + buffer = newBuffer; + } + + buffer.put((byte) b); + } +} \ No newline at end of file diff --git a/src/org/rsbot/log/SystemConsoleHandler.java b/src/org/rsbot/log/SystemConsoleHandler.java new file mode 100644 index 0000000..587216f --- /dev/null +++ b/src/org/rsbot/log/SystemConsoleHandler.java @@ -0,0 +1,13 @@ +package org.rsbot.log; + +import java.util.logging.ConsoleHandler; + +/** + * Logs to System.out + */ +public class SystemConsoleHandler extends ConsoleHandler { + public SystemConsoleHandler() { + super(); + setOutputStream(System.out); + } +} diff --git a/src/org/rsbot/log/TextAreaLogHandler.java b/src/org/rsbot/log/TextAreaLogHandler.java new file mode 100644 index 0000000..19ccf66 --- /dev/null +++ b/src/org/rsbot/log/TextAreaLogHandler.java @@ -0,0 +1,25 @@ +package org.rsbot.log; + +import org.rsbot.gui.component.LogTextArea; + +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class TextAreaLogHandler extends Handler { + + public static final LogTextArea TEXT_AREA = new LogTextArea(); + + @Override + public void close() throws SecurityException { + } + + @Override + public void flush() { + } + + @Override + public void publish(final LogRecord record) { + TextAreaLogHandler.TEXT_AREA.log(record); + } + +} diff --git a/src/org/rsbot/script/AccountStore.java b/src/org/rsbot/script/AccountStore.java new file mode 100644 index 0000000..d692352 --- /dev/null +++ b/src/org/rsbot/script/AccountStore.java @@ -0,0 +1,254 @@ +package org.rsbot.script; + +import org.rsbot.gui.AccountManager; +import org.rsbot.security.RestrictedSecurityManager; +import org.rsbot.util.Base64; +import org.rsbot.util.StringUtil; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +/** + * @author Jacmob + * @author Timer + */ +public class AccountStore { + + public static class Account { + + private final String username; + private String password; + private final Map attributes = new TreeMap(); + + public Account(final String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getAttribute(final String key) { + return attributes.get(key); + } + + public void setAttribute(final String key, final String value) { + attributes.put(key, value); + } + + public void setPassword(final String password) { + this.password = password; + } + + @Override + public String toString() { + return "Account[" + username + "]"; + } + + } + + public static final String KEY_ALGORITHM = "DESede"; + public static final String CIPHER_TRANSFORMATION = "DESede/CBC/PKCS5Padding"; + public static final int FORMAT_VERSION = 2; + + private final File file; + private byte[] digest; + private final String[] protectedAttributes = {"pin"}; + + private final Map accounts = new TreeMap(); + + public AccountStore(final File file) { + if (((RestrictedSecurityManager) System.getSecurityManager()).isCallerScript()) { + throw new SecurityException(); + } + final StackTraceElement[] s = Thread.currentThread().getStackTrace(); + if (s.length < 3 || + !s[0].getClassName().equals(Thread.class.getName()) || + !s[1].getClassName().equals(AccountStore.class.getName()) || + !s[2].getClassName().equals(AccountManager.class.getName())) { + throw new SecurityException(); + } + this.file = file; + } + + public Account get(final String username) { + return accounts.get(username); + } + + public void remove(final String username) { + accounts.remove(username); + } + + public void add(final Account account) { + accounts.put(account.username, account); + } + + public Collection list() { + return accounts.values(); + } + + public void load() throws IOException { + if (!file.exists()) { + file.createNewFile(); + } + if (!file.canRead() || !file.canWrite()) { + file.setReadable(true); + file.setWritable(true); + } + final BufferedReader br = new BufferedReader(new FileReader(file)); + try { + final int v = Integer.parseInt(br.readLine()); + if (v != FORMAT_VERSION) { + throw new IOException("unsupported format version: " + v); + } + } catch (final NumberFormatException ex) { + throw new IOException("bad format"); + } + accounts.clear(); + Account current = null; + for (; ;) { + final String line = br.readLine(); + if (line == null) { + break; + } + if (line.startsWith("[") && line.endsWith("]")) { + if (current != null) { + accounts.put(current.username, current); + } + final String name = AccountStore.fixName(line.trim().substring(1).substring(0, line.length() - 2)); + current = new Account(name); + continue; + } + if (current != null && line.matches("^\\w+=.+$")) { + final String[] split = line.trim().split("="); + if (split[0].equals("password")) { + current.password = decrypt(split[1]); + } else { + if (Arrays.asList(protectedAttributes).contains(split[0])) { + split[1] = decrypt(split[1]); + } + current.setAttribute(split[0], split[1]); + } + } + } + if (current != null) { + accounts.put(current.username, current); + } + br.close(); + } + + public void save() throws IOException { + final BufferedWriter bw = new BufferedWriter(new FileWriter(file)); + bw.write(Integer.toString(FORMAT_VERSION)); + bw.newLine(); + for (final String name : accounts.keySet()) { + bw.append("[").append(AccountStore.fixName(name.trim())).append("]"); + bw.newLine(); + final String password = accounts.get(name).password; + if (password != null) { + bw.append("password="); + bw.append(encrypt(password)); + } + bw.newLine(); + for (final Map.Entry entry : accounts.get(name).attributes.entrySet()) { + final String key = entry.getKey(); + String value = entry.getValue(); + if (Arrays.asList(protectedAttributes).contains(key)) { + value = encrypt(value); + } + bw.append(key).append("=").append(value); + bw.newLine(); + } + } + bw.close(); + } + + public void setPassword(final String password) { + if (password == null) { + digest = null; + return; + } + try { + final MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(password.getBytes("iso-8859-1"), 0, password.length()); + digest = md.digest(); + } catch (final Exception e) { + throw new RuntimeException("Unable to digest password!"); + } + digest = Arrays.copyOf(digest, 24); + for (int i = 0, off = 20; i < 4; ++i) { + digest[off++] = digest[i]; + } + } + + private String encrypt(final String data) { + if (digest == null) { + final byte[] enc = Base64.encodeBase64(StringUtil.getBytesUtf8(data)); + return StringUtil.newStringUtf8(enc); + } + final SecretKey key = new SecretKeySpec(digest, KEY_ALGORITHM); + final IvParameterSpec iv = new IvParameterSpec(new byte[8]); + + byte[] enc; + try { + final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + enc = cipher.doFinal(StringUtil.getBytesUtf8(data)); + } catch (final Exception e) { + throw new RuntimeException("Unable to encrypt data!"); + } + return StringUtil.newStringUtf8(Base64.encodeBase64(enc)); + } + + private String decrypt(final String data) throws IOException { + if (digest == null) { + final byte[] enc = Base64.decodeBase64(StringUtil.getBytesUtf8(data)); + return StringUtil.newStringUtf8(enc); + } + final SecretKey key = new SecretKeySpec(digest, KEY_ALGORITHM); + final IvParameterSpec iv = new IvParameterSpec(new byte[8]); + + byte[] dec; + try { + final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + dec = cipher.doFinal(Base64.decodeBase64(data)); + } catch (final Exception e) { + throw new IOException("Unable to decrypt data!"); + } + return StringUtil.newStringUtf8(dec); + } + + /** + * Capitalizes the first character and replaces spaces with underscores. + * Purely aesthetic. + * + * @param name The name of the account + * @return Fixed name + */ + public static String fixName(String name) { + if (name.contains("@")) { + name = name.toLowerCase().trim(); + } else { + if (name.charAt(0) > 91) { + name = (char) (name.charAt(0) - 32) + name.substring(1); + } + name = name.replaceAll("\\s", "_"); + } + return name; + } + +} + diff --git a/src/org/rsbot/script/BackgroundScript.java b/src/org/rsbot/script/BackgroundScript.java new file mode 100644 index 0000000..e57480e --- /dev/null +++ b/src/org/rsbot/script/BackgroundScript.java @@ -0,0 +1,109 @@ +package org.rsbot.script; + +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.methods.Methods; + +import java.util.EventListener; + +/** + * A background script. + * + * @author Timer + */ +public abstract class BackgroundScript extends Methods implements EventListener, Runnable { + protected String name = ""; + private volatile boolean running = false; + private int id = -1; + public abstract boolean activateCondition(); + + public abstract int loop(); + + public abstract int iterationSleep(); + + public boolean onStart() { + return true; + } + + public void onFinish() { + + } + + @Override + public final void init(final MethodContext ctx) { + super.init(ctx); + onStart(); + } + + /** + * Runs the background script. + */ + public final void run() { + name = getClass().getAnnotation(ScriptManifest.class).name(); + ctx.bot.getEventManager().addListener(this); + running = true; + try { + while (running) { + if (activateCondition()) { + final boolean start = onStart(); + if (start) { + while (running) { + final int timeOut = loop(); + if (timeOut == -1) { + break; + } + Thread.sleep(timeOut); + } + onFinish(); + } + } + Thread.sleep(iterationSleep()); + } + } catch (final Exception e) { + e.printStackTrace(); + } + ctx.bot.getEventManager().removeListener(this); + running = false; + } + + /** + * Removes the script. + * + * @param id The id to deactivate. + */ + public final void deactivate(final int id) { + if (id != this.id) { + throw new IllegalStateException("Invalid id!"); + } + running = false; + } + + /** + * Gives the script an id. + * + * @param id The id. + */ + public final void setID(final int id) { + if (this.id != -1) { + throw new IllegalStateException("Already added to pool!"); + } + this.id = id; + } + + /** + * Gets the id of the script. + * + * @return The ID. + */ + public final int getID() { + return id; + } + + /** + * Checks if the script is running. + * + * @return true if true. + */ + public final boolean isRunning() { + return running; + } +} diff --git a/src/org/rsbot/script/Random.java b/src/org/rsbot/script/Random.java new file mode 100644 index 0000000..42b6fa4 --- /dev/null +++ b/src/org/rsbot/script/Random.java @@ -0,0 +1,155 @@ +package org.rsbot.script; + +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.methods.Methods; +import org.rsbot.service.Monitoring; +import org.rsbot.service.Monitoring.Type; + +import java.awt.*; +import java.util.logging.Level; + +public abstract class Random extends Methods implements PaintListener { + + protected String name; + + private volatile boolean enabled = true; + + public int i = 50; + + public boolean up = false; + + private Script script; + + private final long timeout = random(240, 300); + + /** + * Detects whether or not this anti-random should + * activate. + * + * @return true if the current script + * should be paused and control passed to this + * anti-random's loop. + */ + public abstract boolean activateCondition(); + + public abstract int loop(); + + + /** + * Called after the method providers for this Random + * become available for use in initialization. + */ + public void onStart() { + + } + + public void onFinish() { + + } + + /** + * Override to provide a time limit in seconds for + * this anti-random to complete. + * + * @return The number of seconds after activateCondition + * returns true before the anti-random should be + * detected as having failed. If this time is reached + * the random and running script will be stopped. + */ + public long getTimeout() { + return timeout; + } + + @Override + public final void init(final MethodContext ctx) { + super.init(ctx); + onStart(); + } + + public final boolean isActive() { + return script != null; + } + + public final boolean isEnabled() { + return enabled; + } + + public final void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Stops the current script; player can be logged out before + * the script is stopped. + * + * @param logout true if the player should be logged + * out before the script is stopped. + */ + protected void stopScript(final boolean logout) { + script.stopScript(logout); + } + + public final void run(final Script ctx) { + script = ctx; + name = getClass().getAnnotation(ScriptManifest.class).name(); + ctx.ctx.bot.getEventManager().removeListener(ctx); + for (final Script s : ctx.delegates) { + ctx.ctx.bot.getEventManager().removeListener(s); + } + ctx.ctx.bot.getEventManager().addListener(this); + log("Random event started: " + name); + Monitoring.pushState(Type.RANDOM, "START", name); + long timeout = getTimeout(); + if (timeout > 0) { + timeout *= 1000; + timeout += System.currentTimeMillis(); + } + while (ctx.isRunning()) { + try { + final int wait = loop(); + if (wait == -1) { + break; + } else if (timeout > 0 && System.currentTimeMillis() >= timeout) { + log.warning("Time limit reached for " + name + "."); + Monitoring.pushState(Type.RANDOM, "FAIL", name); + ctx.stopScript(); + } else { + sleep(wait); + } + } catch (final Exception ex) { + log.log(Level.SEVERE, "Uncaught exception: ", ex); + break; + } + } + script = null; + onFinish(); + log("Random event finished: " + name); + Monitoring.pushState(Type.RANDOM, "END", name); + ctx.ctx.bot.getEventManager().removeListener(this); + sleep(1000); + ctx.ctx.bot.getEventManager().addListener(ctx); + for (final Script s : ctx.delegates) { + ctx.ctx.bot.getEventManager().addListener(s); + } + } + + @Override + public final void onRepaint(final Graphics g) { + final Point p = mouse.getLocation(); + final int w = game.getWidth(), h = game.getHeight(); + if (i >= 70 && !up) { + i--; + } else { + i++; + up = i < 130; + } + g.setColor(new Color(0, 255, 0, i)); + g.fillRect(0, 0, p.x - 1, p.y - 1); + g.fillRect(p.x + 1, 0, w - (p.x + 1), p.y - 1); + g.fillRect(0, p.y + 1, p.x - 1, h - (p.y - 1)); + g.fillRect(p.x + 1, p.y + 1, w - (p.x + 1), h - (p.y - 1)); + g.setColor(Color.RED); + g.drawString("Random Active: " + name, 540, 20); + } +} diff --git a/src/org/rsbot/script/Script.java b/src/org/rsbot/script/Script.java new file mode 100644 index 0000000..adcebd7 --- /dev/null +++ b/src/org/rsbot/script/Script.java @@ -0,0 +1,364 @@ +package org.rsbot.script; + +import org.rsbot.Configuration; +import org.rsbot.bot.Bot; +import org.rsbot.event.EventMulticaster; +import org.rsbot.event.listeners.PaintListener; +import org.rsbot.gui.AccountManager; +import org.rsbot.script.internal.BreakHandler; +import org.rsbot.script.methods.MethodContext; +import org.rsbot.script.methods.Methods; +import org.rsbot.script.randoms.LoginBot; +import org.rsbot.script.util.Timer; +import org.rsbot.service.Monitoring; +import org.rsbot.service.Monitoring.Type; + +import java.io.File; +import java.util.EventListener; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; + +public abstract class Script extends Methods implements EventListener, Runnable { + Set