RE Series Part 1: A wild LUA appears !

Topics and discussion about specific mods
ritonlajoie
Inserter
Inserter
Posts: 46
Joined: Tue Mar 29, 2016 10:31 am
Contact:

RE Series Part 1: A wild LUA appears !

Post by ritonlajoie »

Hi guys !

I discovered factorio a few weeks ago and have been playing only in multiplayer since then. The amount of freedom we have in this game is pretty damn high !
As a server owner, I decided to unlock LUA in the factorio executable in order to have full access to LUA from the mods. As you may know, some modules like "io" and "os" are locked to prevent mods doing awful things to your computer. But I believe more in freedom than in lockups, and also I'm getting my hands dirty in learning reverse engineering, so I decided to work on this. I will be using the Factorio .30 experimental version, 64bits, on Windows.

This thread is an educational one, you won't find any modified binary here. If you want to patch your binaries yourself, you can follow my instructions and try to do it yourself, you will have great fun doing it !

I hope to make this into a serie, as I have more plans for factorio, that I will talk to you about later :)
Disclamer: I am not a modder myself, and I known almost nothing about LUA. I may write errors here, please let me know if I say something wrong.

This first episode is about unlocking LUA. What would any mod need to use those "io" and "os" modules ?
Check the lua documentation for these modules :
io : http://www.lua.org/manual/5.1/manual.html#5.7 basically read/write files. Could be useful for statistic gathering, mod syncing on multiplayer, etc..
os : http://www.lua.org/manual/5.1/manual.html#5.8 launch processes from your mods, get the system date & time, get environment variables, etc...


1) Finding an entry point for our research :
When we try to use the "io" module from within a mod, the game launches with this error : Require doesn't work from console and from remote.call.
That means there is some check in factorio which checks if the mods use the "require" lua keyword, and prompts this error when it happens. Then the game quits.
We are going to disable this check, so that "require" works again.

2) Disassembling factorio.exe :
Factorio is an executable which contains lua inside it. If you watch your other games folders, you might see exe files as well as dll files. For other games using lua, you might see lua.dll near the game dll. This is not the case here. That means that the source code of lua has been compiled with the other factorio game files.
With IDA (https://www.hex-rays.com/products/ida/s ... ware.shtml) we can disassemble the executable and see what's inside (if you know ASM and C languages)

First , launch IDA then open the factorio executable located in your steam installation. For me it's in D:\Program Files (x86)\Steam\steamapps\common\Factorio\bin\x64\factorio.exe

IDA will ask you to load a file with some parameters, just keep the default ones. When it asks you to use the debug informations, say yes. That helps us in our research.

Image
Image

The diassembling process will take some time depending on your CPU performance. It's a long process, because each instructions in the executable have to be decoded to ASM ones, and IDA also makes links and resolves references between every function call that happen in the process. To see if the analysis is finished, look in the bottom left of the IDA window. If you see "AC:XXXX" changing, i means it hasn't finished :) When you see 'AU: idle", it's done !

IDA then points you to the entry point of the executable. The first line of code (a funcion) that is executed if you launch it. In our case, it's wmainCRTStartup()

Image

Now, let's go hunt if the error message "Require doesn't work from console" is actually contained in our executable.
For this, open the 'strings' subview in IDA (Menu View -> Open subviews -> Strings).
The "String window" appears. Type 'Alt + T' to search inside, and search for "from console". It's here !

Image

Now double click on this line, it will show you how it's declared in the executable :

Image

Code: Select all

string : .rdata:0000000140948428 00000038 C Require doesn't work from console and from remote.call.
Now, we need to see where this string is being used. What is the function that calls it ?
Right click on it's name and select "jump to xref to operand...".
Image
Image
It seems it's used only in one function (we are lucky !).

Code: Select all

signed __int64 __fastcall LuaHelper::packageRequire(lua_State *L)
Click "OK" to go to this function, or use the 'functions window' to find it.

Here is where IDA jumps to :

Code: Select all

.text:00000001403939D5 loc_1403939D5:                          ; CODE XREF: LuaHelper::packageRequire(lua_State *)+446j
.text:00000001403939D5                 lea     r8, [rsp+168h+name]
.text:00000001403939DA                 cmp     [rsp+168h+name.baseclass_0.baseclass_0._Myres], 10h
.text:00000001403939E0                 cmovnb  r8, qword ptr [rsp+168h+name.baseclass_0.baseclass_0._Bx]
.text:00000001403939E6                 lea     rdx, aModuleSNotFoun ; "module %s not found; "
.text:00000001403939ED                 lea     rcx, [rsp+168h+message] ; result
.text:00000001403939F2                 call    ?ssprintf@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PEBDZZ ; ssprintf(char const *,...)
.text:00000001403939F7                 nop
.text:00000001403939F8                 xor     r14d, r14d
.text:00000001403939FB                 mov     [rsp+168h+path.package], r14
.text:0000000140393A03                 mov     [rsp+168h+path.path.m_pathname.baseclass_0.baseclass_0._Myres], 7
.text:0000000140393A0F                 mov     [rsp+168h+path.path.m_pathname.baseclass_0.baseclass_0._Mysize], r14
.text:0000000140393A17                 mov     word ptr [rsp+168h+path.path.m_pathname.baseclass_0.baseclass_0._Bx], r14w
.text:0000000140393A20                 lea     rdx, [rsp+168h+path] ; path
.text:0000000140393A28                 mov     rcx, r13        ; L
.text:0000000140393A2B                 call    ?loadPackagePath@LuaHelper@@YAXPEAUlua_State@@AEAVPackagePath@@@Z ; LuaHelper::loadPackagePath(lua_State *,PackagePath &)
.text:0000000140393A30                 cmp     [rsp+168h+path.package], r14
.text:0000000140393A38                 jnz     loc_140393ACE
.text:0000000140393A3E                 mov     [rsp+168h+result.baseclass_0.baseclass_0._Myres], 0Fh
.text:0000000140393A4A                 mov     [rsp+168h+result.baseclass_0.baseclass_0._Mysize], r14
.text:0000000140393A52                 mov     byte ptr [rsp+168h+result.baseclass_0.baseclass_0._Bx], r14b
.text:0000000140393A5A                 lea     r8d, [r14+37h]  ; _Count
.text:0000000140393A5E                 lea     rdx, aRequireDoesnTW ; "Require doesn't work from console and f"...
.text:0000000140393A65                 lea     rcx, [rsp+168h+result] ; this
.text:0000000140393A6D                 call    ?assign@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QEAAAEAV12@PEBD_K@Z ; std::basic_string<char,std::char_traits<char>,std::allocator<char>>::assign(char const *,unsigned __int64)
.text:0000000140393A72                 nop
.text:0000000140393A73                 lea     rax, [rsp+168h+result]
.text:0000000140393A7B                 cmp     [rsp+168h+result.baseclass_0.baseclass_0._Myres], 10h
.text:0000000140393A84                 cmovnb  rax, qword ptr [rsp+168h+result.baseclass_0.baseclass_0._Bx]
.text:0000000140393A8D                 mov     [rsp+168h+_What], rax
.text:0000000140393A95                 lea     rdx, [rsp+168h+_What] ; _What
.text:0000000140393A9D                 lea     rcx, [rsp+168h+pExceptionObject] ; this
.text:0000000140393AA5                 call    ??0exception@std@@QEAA@AEBQEBD@Z ; std::exception::exception(char const * const &)
.text:0000000140393AAA                 lea     rax, ??_7ScriptException@@6B@ ; const ScriptException::`vftable'
.text:0000000140393AB1                 mov     [rsp+168h+pExceptionObject.vfptr], rax
.text:0000000140393AB9                 lea     rdx, _TI4?AVScriptException@@ ; pThrowInfo
.text:0000000140393AC0                 lea     rcx, [rsp+168h+pExceptionObject] ; pExceptionObject
.text:0000000140393AC8                 call    _CxxThrowException
.text:0000000140393AC8 ; ---------------------------------------------------------------------------
.text:0000000140393ACD                 align 2
.text:0000000140393ACE
What this function does, is that it reads the "require" instructions from the mod file content (the .lua file) and checks if there is a "require" inside.
When it encounters a "require", an exception is thrown :

Code: Select all

.text:000000013FC53AC8                 call    _CxxThrowException
We want to stop the exception being thrown, so we just NOP out this "call" in the executable. Using an hexadecimal editor, just open factorio.exe, go to address 13FC53AC8 and write 6 bytes NOP (write 90 90 90 90 90 90) instead of what is already here. This will change the "call _CxxThrowException" to "NOP NOP NOP NOP NOP NOP" so that the CPU does nothing, instead of calling this _CxxThrowException function.

If you save your file now, add a "require io" in the top of a mod, you will not see the error again.

But let's go further : why not including "io" and "os" automagically so that you don't have to require them in every mod ? After all, it's part of the lua stdlib.

3) enabling "io" and "os" :
LUA being an embedded library in factorio, we can check the lua functions being using from withing factorio.
For example there is this lua_setglobal function used in some locations in the executable (you can check with IDA). To communicate between factorio and the LUA virtual machine, LUA exposes an API (C functions) which uses the stack. To disable a library in lua, like the io one, programmers can use lua_setglobal.

http://stackoverflow.com/questions/1010 ... -lua-heade

For example they will create a variable and set it to zero, then say : the "io" global variable will be zero. This is done in factorio using this code :

I believe now I don't have to put screenshots of how to find that, because you probably know how to do it, right ?

Code: Select all

Function : LuaHelper::initLuaState(lua_State *L, int a2, __int64 a3, int a4)
Code : 
v4->top;
v4->top->tt_ = 0;
++v4->top;
lua_setglobal(v4, "io")
The same is done for "os".

Using the previous NOP technique, we juste have to disable those lua_setgloabl calls when they are used with "io" and "os".

4) Testing :
Download any mod from the forum, open a .lua file from it, an in a function, add :

file = io.open("c:\\temp\\toto.txt", "w")
io.close(file)

Launch factorio, and play a new game. You will see an empty "toto" file created in your c:\temp folder.

Next step ? Surprise ! hint : snakes bites !
User avatar
Adil
Filter Inserter
Filter Inserter
Posts: 945
Joined: Fri Aug 15, 2014 8:36 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by Adil »

I guess everyone is too amazed, shocked or horified by blasphemy you've conducted and the powers you mingle with. Or it's that you've just posted to the dedest region of the forums.

So, can we do anything except rm -rf-ing ourselves with these tricks? Like connecting the actual terminal to the game or customizing syntax?
I do mods. Modding wiki is friend, it teaches how to mod. Api docs is friend too...
I also update mods, some of them even work.
Recently I did a mod tutorial.
orzelek
Smart Inserter
Smart Inserter
Posts: 3922
Joined: Fri Apr 03, 2015 10:20 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by orzelek »

Tbh I'm waiting for devs to notice and react to this one :)
Also I wouldn't run a mod taht would require this without validating the code in it.

It's a curiosity that I don't see used much tbh.
Koub
Global Moderator
Global Moderator
Posts: 7778
Joined: Fri May 30, 2014 8:54 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by Koub »

Well there's no explicit "do not reverse engineer" in the Terms of Service.
I'll let the devs express themselves.
Koub - Please consider English is not my native language.
User avatar
MrGrim
Fast Inserter
Fast Inserter
Posts: 241
Joined: Sat Apr 09, 2016 7:58 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by MrGrim »

I could see this technique maybe being used to make something like Skyrim's SKSE for Factorio hooking into game functions from Lua to enhance the capabilities of mods.
Choumiko
Smart Inserter
Smart Inserter
Posts: 1352
Joined: Fri Mar 21, 2014 10:51 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by Choumiko »

Koub wrote:I'll let the devs express themselves.
I bet if they do, the reply contains "determinism" and "desync" which is one of the reasons for io and os not being available :D
User avatar
y.petremann
Filter Inserter
Filter Inserter
Posts: 421
Joined: Mon Mar 17, 2014 4:24 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by y.petremann »

Choumiko wrote:
Koub wrote:I'll let the devs express themselves.
I bet if they do, the reply contains "determinism" and "desync" which is one of the reasons for io and os not being available :D
I'm fine with that idea that it would cause desync, but does writing in a texfield would not cause desync ?

I think that they should open a bit because for now, my only way to make communication between game and other software is using output files and a bot inputing in the chat ...
ritonlajoie
Inserter
Inserter
Posts: 46
Joined: Tue Mar 29, 2016 10:31 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by ritonlajoie »

oh guys, some activity on this thread ? I thought it was dead by now :)
I wonder, about the desync.. if a mod writes to a file and others don't, does it cause desync ? As far as I understood, only game state changing would cause desync, right ?
MrDoomah
Fast Inserter
Fast Inserter
Posts: 196
Joined: Mon Jun 01, 2015 1:11 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by MrDoomah »

y.petremann wrote:
Choumiko wrote:
Koub wrote:I'll let the devs express themselves.
I bet if they do, the reply contains "determinism" and "desync" which is one of the reasons for io and os not being available :D
I'm fine with that idea that it would cause desync, but does writing in a texfield would not cause desync ?

I think that they should open a bit because for now, my only way to make communication between game and other software is using output files and a bot inputing in the chat ...
Writing in a textfield does not cause desyncs because the text is send to all other players/host.

Loading a file can cause desync because some players might not have that file or have a different version of that file.
daniel34
Global Moderator
Global Moderator
Posts: 2761
Joined: Thu Dec 25, 2014 7:30 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by daniel34 »

ritonlajoie wrote:I wonder, about the desync.. if a mod writes to a file and others don't, does it cause desync ? As far as I understood, only game state changing would cause desync, right ?
No, writing to the file won't cause a desync and there's actually a lua function for it:
http://lua-api.factorio.com/0.12.30/Lua ... write_file
This will only write to the script-output directory in the factorio data directory (appdata).

Reading from a file however could cause a desync because of different file contents, which is why there is no such function for reading.

Related from this thread:
Rseding91 wrote:The standard game is not getting un-sandboxed.

I was thinking about it and had the idea that we might provide a flag that mods could use or perhaps a startup flag that would be required that enabled all of the unsafe and non-deterministic methods that aren't normally available. It would at the same time disable multiplayer and replays but this is all just ideas - I'm not sure if it would be worth the time at this point.
quick links: log file | graphical issues | wiki
User avatar
y.petremann
Filter Inserter
Filter Inserter
Posts: 421
Joined: Mon Mar 17, 2014 4:24 pm
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by y.petremann »

ritonlajoie wrote:oh guys, some activity on this thread ? I thought it was dead by now :)
I wonder, about the desync.. if a mod writes to a file and others don't, does it cause desync ? As far as I understood, only game state changing would cause desync, right ?
writing is not really a problem, it's reading, if players had diferent contents in their respective files, then it would make desync.

For me I think that reading files should be done this way :
  • Ask for file read with player, file name (limiting to factorio folder), size limit, and custom arguments, it would register an event which would be fired when the file is transfered to everybody and ready to be used.
  • Players would be asked to load the file if it doesn't correspond to his preferences (binary data ? heavy file ?)
  • Fire the event function, the event function is in charge of detecting if it's for him to handle the file, custom arguments are for this type of things.
ritonlajoie
Inserter
Inserter
Posts: 46
Joined: Tue Mar 29, 2016 10:31 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by ritonlajoie »

MrDoomah wrote:
y.petremann wrote:
Choumiko wrote:
Koub wrote:I'll let the devs express themselves.
I bet if they do, the reply contains "determinism" and "desync" which is one of the reasons for io and os not being available :D
I'm fine with that idea that it would cause desync, but does writing in a texfield would not cause desync ?

I think that they should open a bit because for now, my only way to make communication between game and other software is using output files and a bot inputing in the chat ...
Writing in a textfield does not cause desyncs because the text is send to all other players/host.

Loading a file can cause desync because some players might not have that file or have a different version of that file.
ok so this is a non-issue, solved problem, with file hashes
User avatar
Blu3wolf
Fast Inserter
Fast Inserter
Posts: 203
Joined: Thu Apr 09, 2015 5:20 am
Contact:

Re: RE Series Part 1: A wild LUA appears !

Post by Blu3wolf »

Interesting, because a number of mods require other files normally in factorio. I suspect the assumptions made here are not entirely valid.
Post Reply

Return to “Mods”