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.


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 !
 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()

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 !

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

Code: Select all
string : .rdata:0000000140948428 00000038 C Require doesn't work from console and from remote.call.Right click on it's name and select "jump to xref to operand...".


It seems it's used only in one function (we are lucky !).
Code: Select all
signed __int64 __fastcall LuaHelper::packageRequire(lua_State *L)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
When it encounters a "require", an exception is thrown :
Code: Select all
.text:000000013FC53AC8                 call    _CxxThrowExceptionIf 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")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 !









