网上冲浪时遇到的 APP,FIMO 家的同款相机软件,但是没有更新,官网也无了,只有两个可模拟的胶片,试图为其增加新的胶片效果
APK 文件结构
在 apk 的 assets/static 目录下存在下面文件结构:
bash.├── camera│ └── PLCamera1│ ├── camera-cover.png│ ├── camera-photo-shadow.png│ └── cover.png├── camera.json├── default.json├── images│ └── lens-cover1.png├── of│ ├── Color600.zip│ ├── Frame-1.zip│ ├── MixPhoto.zip│ └── TimeZero.zip├── of.json├── paper│ ├── PLPaper1│ │ ├── cover.png│ │ ├── desc-en.png│ │ ├── desc-zh.png│ │ ├── display-cover-preview.png│ │ ├── display-cover.png│ │ ├── sample-1.png│ │ ├── sample ...│ └── PLPaper2│ ├── cover.png│ ├── ...└── paper.json
通过使用 APP 可以判断 Color600.zip 和 TimeZero.zip 属于胶片文件,提供不同的胶片效果,所以再看看 zip 有什么
bash.├── BokehNoDepth│ └── BokehNoDepth.oflua├── FilmGrains2.0│ ├── FilmGrains2.0.oflua│ ├── noise.png│ └── noise2.png├── TZ160D.png├── TZ160DDA.png├── Thumbs.db // 缓存文件,忽略├── backblur│ └── backblur.oflua└── effect0.ofeffect
根据对比可以判断不同的效果是通过其中的 lua 脚本实现的,对比effect0.ofeffect这个配置文件:

可以发现三个 oflua 脚本和其使用的 png 图像实现了具体的滤镜效果
- BokehNoDepth
- BokehNoDepth.oflua:Lua 脚本,用于实现无深度的散景效果。在
effect0.ofeffect文件中,它与自定义滤镜(CustomLuaFilter)关联,用来调整模糊度(Blur)、伽马(Gamma)、半径(Radius)、柔和度(Softness)等参数。
- BokehNoDepth.oflua:Lua 脚本,用于实现无深度的散景效果。在
- FilmGrains2.0
- FilmGrains2.0.oflua:Lua 脚本,用于创建电影颗粒效果。它在
effect0.ofeffect文件中作为自定义滤镜的一部分,控制颗粒量和颗粒大小。 - noise.png 和 noise2.png:用于生成颗粒效果的噪声纹理图像。
- FilmGrains2.0.oflua:Lua 脚本,用于创建电影颗粒效果。它在
- backblur
- backblur.oflua:这是另一个 Lua 脚本,用于实现背景模糊效果。在
effect0.ofeffect文件中,它控制模糊半径和模糊步骤。
- backblur.oflua:这是另一个 Lua 脚本,用于实现背景模糊效果。在
- effect0.ofeffect 用于调整胶片效果的参数配置
再来一个!
修改camera.json、of.json、paper.json配置文件中的内容,增加新的胶片效果helloworld
修改 zip 里的FilmGrains2.0目录下的两张噪声图像,经过检验噪声图像会在处理源图像时随机叠加在图像上
修改 zip 里的两张PNG图像,这里为其简单添加一个黑白滤镜效果以验证可行性
修改 aip 里的effect0.ofeffect配置文件的参数
将新的 zip 文件以及更新后的camera.json、of.json、paper.json、PLPaperX文件夹重新打包即可,示例效果如下:

确实是变黑白了,噪声图像在成像中也可见
代码里有什么?
通过搜索 static/ 可以找到一些代码对其的使用:
javaResources resources = this.c.getResources();e eVar = e.a;f0.a(resources, "resources");String a = eVar.a("static/camera.json", resources);String a2 = e.a.a("static/paper.json", resources);String a3 = e.a.a("static/of.json", resources);String a4 = e.a.a("static/default.json", resources);Gson gson = new Gson();try {d.i().addAll((Collection) gson.fromJson(a, new a().getType()));d.k().addAll((Collection) gson.fromJson(a2, new b().getType()));d.j().addAll((Collection) gson.fromJson(a3, new c().getType()));defaultCameraInfo = (DefaultCameraInfo) gson.fromJson(a4, DefaultCameraInfo.class);} catch (Throwable e) {d.g.a();g.j.a.a.g.c.a(obj, e);return c1.a;}
大概的过程:
- 初始化 HashMap 和 ArrayList:
HashMap<String, Camera> cameraMapHashMap<String, Paper> paperMapHashMap<String, Of> ofMap
- 加载和解析 JSON:
loadModels方法会加载和解析static/camera.json,static/paper.json,和static/of.json中的数据。
- JSON 解析:
- 使用
Gson库将 JSON 数据解析为 Java 对象,存储在前面创建的 HashMap 中。 - 例如,对于
Of对象,使用的是TypeToken<ArrayList<Of>>来指定Gson如何将 JSON 字符串解析成Of对象列表。
- 使用
of.json文件中的数据被解析为一个ArrayList类型的C2054Of对象列表。这个过程主要发生在init方法的异步执行部分。
下面是具体的处理步骤和逻辑:
解析
of.json文件
加载和解析 JSON:
- 使用
Gson对象调用fromJson方法,将从资源中读取的of.json文件的内容解析为Of对象的列表。- 使用
TypeToken来指定Gson需要转换成的具体类型,这里是ArrayList<Of>。存储解析后的数据:
- 将解析得到的
Of列表添加到f26154f,这是一个静态的ArrayList,用于存储Of对象。处理解析后的
Of对象
设置文件路径:
- 遍历解析后的
C2054Of列表,为每个C2054Of对象设置正确的文件路径。这是通过获取应用缓存文件目录的路径并与Of对象中的fileName属性结合来实现的,确保每个对象都指向其对应的文件。更新
Of对象映射:
- 创建或更新一个
HashMap(f26153e),该映射使用每个Of对象的fileName作为键,Of对象本身作为值。这允许通过文件名快速访问每个Of对象,即 zip 文件整合和应用
Of对象
- 为
Paper和Camera模型设置Of对象:
- 遍历
Paper和Camera对象的列表,根据它们存储的ofListEffect和ofListBorder(这些列表存储了fileName的引用)来从f26153e映射中检索相应的Of对象,并设置为Paper或Camera对象的属性。- 例如,一个
Paper对象可能有一个效果列表(ofListEffect),该代码通过遍历这个列表,并使用列表中的每个文件名从f26153e映射中检索Of对象,然后将它们设置为该Paper对象的ofModelList属性。
哪里的 Lua?
对 apk 内的 .so 进行搜索
bashfor file in *; doif strings "$file" | grep -q lua; thenecho "Found in file $file:"strings "$file" | grep luafidone
结果显示:
bashFound in file libapp.so:_evaluateAssertionevaluate:source_ChainedEvaluation@83105126evaluate__AnimatedEvaluation&Animation&AnimationWithParentMixin@831051268_AnimatedEvaluation@83105126startRuleEvaluationevaluateMessage_evaluateAssertion@0150898Invalid number of arguments in evaluate response8Found in file libflutter.so:// Evaluate the cubic at T = (sk_VertexID / 2^kMaxResolveLevel).// dx and dy uniformly, we can throw it out completely after evaluating the derivative_evaluateAssertionevaluate:source// Evaluate the conic weights at T.// Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.// Pack the arguments for the evaluation stage.Found in file liborangefilterjni.so:_ZN12OrangeFilter6LuaCpp28RegisterLuaStateCloseHandlerEP9lua_StatePFvS2_E_ZNK12OrangeFilter14AnimationCurve8evaluateEf_ZNK12OrangeFilter14AnimationCurve12evaluateQuatEf_ZNK12OrangeFilter14AnimationCurve18evaluateMultiValueEfPflua_scriptN12OrangeFilter6LuaCpp19concrete_any_pusherINS0_9luaObjectEEEN12OrangeFilter6LuaCpp3any6holderINS0_9luaObjectEEEorangefilter.lualibThere is no function in lua %slua_pcall default errorCall lua [requiredFrameData] error: %sCall lua [onApplyParams] error: %s...luaopen_%s/usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua/usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.solua_debug>Found in file libvenus2jni.so:lua_script
显而易见,lua 的混淆出现在liborangefilterjni.so中,根据网络上的信息,这个库是YY 内部的设计工具。结合 jadx 中对该 so 的调用,有/* renamed from: com.yy.orangefilter.R */字样出现,可以确定就是该工具
其完善的兄弟产品 FIMO 也使用了liborangefilterjni.so,polr 看起来更像是 FIMO 的 demo?
未知的后续
oflua 的混淆如何实现?还能做什么:
- 追踪相纸 zip 文件在 app 中的调用,app 是如何处理和调用每个 zip 的
- 从文件选择器入手,看每一张相片是如何生成的,图片是如何被处理的
能不能将 FIMO 中数量巨大的胶片效果移植到 polr 上?
