Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
jac3km4 committed Jan 8, 2022
1 parent 4d6a78b commit 1b86d82
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.bloop/
.metals/
.vscode/
out/
25 changes: 25 additions & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
rules = [
DisableSyntax,
OrganizeImports
]

DisableSyntax {
noVars = true,
noThrows = true,
# noNulls = true,
# noWhileLoops = true,
noReturns = true,
noAsInstanceOf = true,
noIsInstanceOf = true,
noDefaultArgs = true,
noFinalVal = true,
noFinalize = true,
noValPatterns = true,
}

OrganizeImports {
removeUnused = false,
coalesceToWildcardImportThreshold = 4,
expandRelative = true,
groupedImports = AggressiveMerge,
}
20 changes: 20 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version = "3.3.1"
runner.dialect = scala3
align.preset = more

maxColumn = 100
newlines.afterCurlyLambdaParams = squash
rewrite.rules = [
SortImports,
RedundantBraces,
SortModifiers,
PreferCurlyFors
]
rewrite.redundantBraces.stringInterpolation = true
rewrite.scala3.removeOptionalBraces = true

fileOverride {
"glob:**/build.sc" {
runner.dialect = scala213
}
}
32 changes: 32 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import $ivy.`com.goyeau::mill-scalafix:0.2.6`

import mill.scalalib._
import mill.scalalib.scalafmt._
import com.goyeau.mill.scalafix.ScalafixModule

object luafu extends ScalaModule with ScalafmtModule with ScalafixModule {
def scalaVersion = "3.1.0"
def scalacOptions = super.scalacOptions() ++ customScalaOpts

def ivyDeps = Agg(
ivy"org.ow2.asm:asm:9.2",
ivy"org.ow2.asm:asm-commons:9.2"
)

def scalafixIvyDeps = super.scalafixIvyDeps() ++ Agg(
ivy"com.github.liancheng::organize-imports:0.6.0"
)

def customScalaOpts =
Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Wunused:all",
"-Xfatal-warnings"
)

def check = T {
T.sequence(Seq(fix("--check"), checkFormat()))
}
}
140 changes: 140 additions & 0 deletions luafu/src/Luafu.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import org.objectweb.asm._
import org.objectweb.asm.tree.{ClassNode, MethodNode}

import java.lang.reflect.{Method, Modifier}
import java.nio.file.{Files, Paths}
import java.util.zip.{ZipEntry, ZipFile}
import scala.collection.mutable.ListBuffer
import scala.io.{Source, StdIn}
import scala.jdk.CollectionConverters._
import scala.util.control.Exception.{allCatch, ultimately}

object Luafu:
def main(args: Array[String]): Unit =
val jar = Jar(ZipFile("lib\\wakfu-client.jar"))
val descriptors = ScriptDescriptors.resolve(jar).get
val context = ScriptContext.load(jar, descriptors).get

getClass.getClassLoader
.loadClass("com.ankamagames.wakfu.client.WakfuClient")
.getMethod("main", classOf[Array[String]])
.invoke(null, Array[String]())

while (true) {
val line = StdIn.readLine
allCatch.withApply(println)(context.interpret(line))
}

case class ScriptDescriptors(
scriptManagerClass: String,
loadScriptDescriptor: String
):
val scriptLibraryClass: String =
Type.getArgumentTypes(loadScriptDescriptor)(1).getElementType.getClassName

val scriptClass: String =
Type.getReturnType(loadScriptDescriptor).getClassName

object ScriptDescriptors:
def resolve(jar: Jar): Option[ScriptDescriptors] =
for
scriptManagerClass <- jar.classes
.map { clazz =>
val finder = FieldConstantFinder(str => str == ".lua" || str == "script")
clazz.accept(finder, Opcodes.ASM9)

if (finder.values.length == 2)
val node = ClassNode()
clazz.accept(node, Opcodes.ASM9)
Some(node)
else None
}
.collectFirst { case Some(res) => res }

loadScriptMethod <-
scriptManagerClass.methods.asScala.find { m =>
val args = Type.getArgumentTypes(m.desc)
val ret = Type.getReturnType(m.desc)

(m.access & Opcodes.ACC_PRIVATE) != 0 &&
(args.length == 3) &&
ret.getSort == Type.OBJECT &&
args(0).getSort == Type.OBJECT &&
args(1).getSort == Type.ARRAY &&
args(2) == Type.BOOLEAN_TYPE
}
yield ScriptDescriptors(scriptManagerClass.name, loadScriptMethod.desc)

class ScriptContext(
scriptManager: AnyRef,
scriptLibraries: AnyRef,
loadScript: Method,
runScript: Method
):
loadScript.setAccessible(true)
private val pattern = """dofile\(['"](.*)['"]\)""".r

def interpret(code: String): Unit =
val source = pattern.findFirstMatchIn(code) match
case Some(res) =>
val file = res.group(1)
val path = Paths.get("luafu").resolve("scripts").resolve(file)
new String(Files.readAllBytes(path))
case None => code

val script = loadScript.invoke(scriptManager, source, scriptLibraries, false)
runScript.invoke(script, java.util.HashMap())

object ScriptContext:
def load(jar: Jar, descriptors: ScriptDescriptors): Option[ScriptContext] =
val managerClass = getClass.getClassLoader.loadClass(descriptors.scriptManagerClass)
val scriptClass = getClass.getClassLoader.loadClass(descriptors.scriptClass)

for
scriptManager <- managerClass.getDeclaredMethods
.find(m => Modifier.isStatic(m.getModifiers) && m.getParameterCount == 0)
.map(_.invoke(null))
scriptLibraries = loadAllLibraries(jar, descriptors)

loadScript <- managerClass.getDeclaredMethods
.find(m => Type.getMethodDescriptor(m) == descriptors.loadScriptDescriptor)
runScript <- scriptClass.getDeclaredMethods
.find(m => m.getParameterTypes.sameElements(Array(classOf[java.util.Map[_, _]])))
yield ScriptContext(scriptManager, scriptLibraries, loadScript, runScript)

def loadAllLibraries(jar: Jar, descriptors: ScriptDescriptors): AnyRef =
val libraryBaseClass = getClass.getClassLoader.loadClass(descriptors.scriptLibraryClass)
val libraryClasses = jar.classes.filter(_.getSuperName == descriptors.scriptLibraryClass)
val array = java.lang.reflect.Array.newInstance(libraryBaseClass, libraryClasses.length)

for ((libClassReader, i) <- libraryClasses.zipWithIndex)
getClass.getClassLoader
.loadClass(libClassReader.getClassName)
.getDeclaredMethods
.find(m => Modifier.isStatic(m.getModifiers) && m.getParameterCount == 0)
.foreach(m => java.lang.reflect.Array.set(array, i, m.invoke(null)))

array

class Jar(zip: ZipFile):
def classes: Iterator[ClassReader] =
zip.entries.asScala
.filter((entry: ZipEntry) => entry.getName.endsWith(".class"))
.map { (entry: ZipEntry) =>
val is = zip.getInputStream(entry)
ultimately(is.close())(ClassReader(is))
}

class FieldConstantFinder(fieldPred: AnyRef => Boolean) extends ClassVisitor(Opcodes.ASM9):
val values = ListBuffer.empty[AnyRef]

override def visitField(
access: Int,
name: String,
descriptor: String,
signature: String,
value: AnyRef
): FieldVisitor =
if (fieldPred(value))
this.values += value
super.visitField(access, name, descriptor, signature, value)
5 changes: 5 additions & 0 deletions resources/launch.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cd ..
$baseCp = (Get-ChildItem -Path "lib" | %{ $_.FullName }) -join ";"
$addedCp = "{0}\luafu\luafu.jar;{1}" -f (pwd),$baseCp

jre\win32\x64\bin\java.exe "-Djava.library.path=natives\win32\x64" -cp $addedCp Luafu
27 changes: 27 additions & 0 deletions resources/scripts/example.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

local playerId = Context.getPlayer()
local x, y, z = Mobile.getMobilePosition(playerId)

Mobile.setMobileAnimation(playerId , "AnimEmote-Presenter")
-- Mobile.setMobileVisible(Context.getPlayer(), false)

Camera.setZoomFactor(2.5)
-- Camera.setUserZoomLocked(true)

-- UI.setUIVisible(false)
UI.loadTutorialDialog("bossSmasher","event.boss.smasher.13.title","event.boss.smasher.13.description")
-- UI.displayBackground(53)

-- display dialogs
-- invoke(0, 1, "UI.displaySplashText","quest.astrub.nation.choix")
invoke(0, 1, "UI.loadTutorialDialog", "beta.shushu", "beta.title", "beta.09.11.2021")

BubbleText.showText(playerId, "testing", 0, 40, false)

Sound.playLocalSound(11, 420800522001, 40, 2, -1, 0)

-- adds a compass location
UI.addCompass("compass", x, y + 1, z)

-- spawns a puddly
Actor.createActor(Mobile.generateClientMobileId(), 122601633, x, y + 1, z, 4)

0 comments on commit 1b86d82

Please sign in to comment.