yashsingh1/xdaforums huggingface dataset:
[Windows Mobile Development and Hacking General] sztupy:
Decompiling tutorial part 3

So, we have cleaned the output, and running compare will not output a compilation error, but a comparison of the two scripts. Let's continue from here.

What you love:

Main block:
Same

What you hate:

Function 3:
1> 11 [-]: GETGLOBAL R1 K0 ; R1 := PeopleListView
...

And if you only get outputs from the first type, then you are very very happy :)

Now, let's start fixing the output.

I always look at the first bug:

Function 3:
1> 11 [-]: GETGLOBAL R1 K0 ; R1 := PeopleListView
2> 11 [-]: GETGLOBAL R0 K0 ; R0 := PeopleListView

Compare will always print out the differences in the two files. odd lines will be from the original, even lines will be from the decompiled script. Let's look at the usual bugs.

In the previous code output you can clearly see, that in the original PeopleListView is assigned to R1, but in out script it's assigned to R0. What does this mean? LOCALS! Yep, you have to declare something as a local in function #3 (which is the fourth(!) function, never forget!) And it's around opcode number 11, which is around the beginning of the file. To fix it you can either search for the string "PeopleListView" around the beginning of function 4, and declare something BEFORE that as a local. If you don't know what to declare as local, then look at the opcode line 10 (or 9, or something), and declare that as local.

For example in our example the original line was:

if PeopleListView:GetLayout() == nil then
return
end
if PeopleListView:GetGenerator() == nil then
return
end

I added some comments:

-- this is opcode number 7. Here the disassembly output is:
-- 7 [-]: CALL R0 2 2 ; R0 := R0(R1)
-- this is where PeopleListView:GetLayout() was declared as a local
if PeopleListView:GetLayout() == nil then
return
end
-- this is opcode number 11. Here is the PeopleListView that is shown in the
-- disassembly output
if PeopleListView:GetGenerator() == nil then
return
end

The fixed output is:

local l_4_0 = PeopleListView:GetLayout()
if l_4_0 == nil then
return
end
if PeopleListView:GetGenerator() == nil then
return
end
[Windows Mobile Development and Hacking General] sztupy:
Decompiling tutorial part 4

The other usual bug is the following:

1> 11 [-]: GETGLOBAL R1 K5 ; R1 := ContactObj
2> 11 [-]: GETGLOBAL R1 K5 ; R1 := MyFavesUtilityObj

Here, in the original ContactObj is first, but in our script MyFavesUtilityObj is first. Lua will put the constants in the compiled output the same order it encounters them in the script. What could the previus bug mean?

Of course LOCALS!

Let's look at the script:

MyFavesUtilityObj:IsMyFavesContactyIDForLua(ContactObj.ContactList:GetOidStr(l_8_0 + 1))

As you can see here is a function call, which has one parameter. And look at it. In out script MyFavesUtilityObj comes first, but in the original output ContactObj should come first.

How to fix? Easy:

local cobj = ContactObj.ContactList:GetOidStr(l_8_0 + 1)
MyFavesUtilityObj:IsMyFavesContactyIDForLua(cobj)

As you can see, now ContactObj is the first thing it will encounter and not MyFavesUtilityObj!

There is one more bug which is usual:

1> 6 [-]: LE 0 K2 R0 ; if 0 > R0 then PC := 69
2> 6 [-]: LE 0 K2 R0 ; if 0 > R0 then PC := 68
1> 7 [-]: JMP 69
2> 7 [-]: JMP 68
1> 9 [-]: EQ 0 R1 K4 ; if R1 != 1 then PC := 30
2> 9 [-]: EQ 0 R1 K4 ; if R1 != 1 then PC := 29
1> 10 [-]: JMP 30
2> 10 [-]: JMP 29

CONDITIONALS!

99 out of 100 cases you will HATE them!

Usually if the only difference between the original and the decompiled script is the location where they jump (the PC:=xxx part) then don't care about them, unless you have fixed the rest of the script (I mean the locals). If there are only conditional statements left, then you will have a hard time guessing how this have to be done. Usually this means the structure of the conditionals are not right. For example instead of

if blah then
if blah then
blah
else
blah
end
end

you have to put:

if blah then
if blah then
blah
end
else
blah
end

And similar. Use common sense in these cases. Usually if you read the script and guess about what it does you will know how to fix these issues. In other cases the disassembly output might help, because it will show you where the jumps are. This is the part of the decompilation process which involves the most thinking usually. But after a while this part gets easy as well :)
[Windows Mobile Development and Hacking General] sztupy:
Decompiling tutorial part 5

Here are two incorrectly decompiled scripts to play with. They doesn't have too much bugs in them, but show the usual errors one have to fix in order to get a fully decompiled script, so they are good for testing the skills you've learned with the previous tutorials :) I didn't touch any of them, so if you manage to succesfully decompile them, you can say that you've already decompiled a script yourselves... all alone :)

I didn't decompile them yet, so if you want to help me out, you might try to do them yourselves, before I get to them ;)
[Windows Mobile Development and Hacking General] sztupy:
305

New additions:

4 embedded scripts

citypicker

simpledialogscript

stock_chart

stock_setting

people/contactobject

people/contactpickerallscript

people/contactpickerscript

people/favoritesetting

people/peopledetailselectnumber

people/peopledialogbase

people/peoplefavoriteglobal

people/peopleselectcontactscript

internetportal/bookmarkedit

internetportal/internetpush_frequencysettings
[Windows Mobile Development and Hacking General] smotrs:
Here's a question, why does the disassembler swap the == and ~= relational operators?

For instance, the disassembled output shows the following,

2 [-]: EQ 0 R2 K1 ; if R2 != "windowsmobile" then PC := 12

but the decompiled output shows the same line like this,

if l_1_2 == "windowsmobile" then
[Windows Mobile Development and Hacking General] smotrs:
Here's another question. Why are the -f options in luadec and compare different values?

For instance, if I run a compare like this,

compare -f 0 1f3be060_manila_1583A.luac f3be060_manila_1583A.luac.lua

I have to run the following to get the same function with luadec,

luadec -f 1 1f3be060_manila_1583A.luac

one is using -f 0 the other -f 1 which is confusing.
[Windows Mobile Development and Hacking General] sztupy:
The VM of lua is not structural. It goes from top to bottom, and if it encounters a JMP operator it will jump to the specified opcode. What this means that if it encounters something like

if blah then
end

then if blah is TRUE then it does nothing, and this is the right thing to do, because if TRUE, then you want to run the things following the if. But, if blah is FALSE then you have to jump through the hole sequence between the "if" and the "end" That's why the disassembled output shows (usually) the inverse what you have to write (it shows when to jump, and not when not to jump).

This is confusing a lot of times unfortunately, and there are a lot of bugs involving this too (this is mostly why the decompiler fails to decompile complex conditionals with lots of 'and', 'or' and 'elseif' parts... Unfortunately that part of the decompiler is not clear to me, the original code of luadec had no comments, and it had a very unreadable code)
[Windows Mobile Development and Hacking General] sztupy:
Yes I know about that... this is unfortunately because luadisasm is 0 based, but luadec is 1 based. Half a while during the decomp I thought I might change this, but I got used to this anomaly so much I was confused after I did the changes... After I finish this whole decomping stuff I might correct this glitch...

Btw I never use the -f option of compare, that was only added so luadecguess could compare by function too. I usually get a good looking output with all the functions, correct it, so it compiles, then run compare without any fancy parameters, and press CTRL+C after it outputs some errors. Then I correct the errors, run compare again, CTRL+C, etc. until compare says "same" for everything.
[Windows Mobile Development and Hacking General] sztupy:
LuaDec 0.7

Hi!

LuaDec 0.7 released.

This release contains a major bugfix considering OP_TFORLOOP fucntionality and conditional handling as well. I hope the changes I made in the conditional handling won't break anything that worked, but it shouldn't (actually it should make decompiling more error-free). A lot of decompilation erros and crashes were caused by these bugs, but now they should be gone...

I'm on EDGE currentyl so I can only release the modified file, will upload the whole package on monday.
[Windows Mobile Development and Hacking General] smotrs:
Can you think of any reason why luadec (version 6 and now 7) with the -l option wouldn't output anything?

I tried it with and without the -l option. With the -l option just prints the following,

C:\Documents and Settings\user1\My Documents\My Software\Mobile Software\lua51_tf3d_beta_6>luadec -l ;;0,11,32;;0,17,38;;;;;;;5;;0,3,6,16,26;0,34;14,35;;;;;;;49,100,221;;0,0,223;;;;21,29;;;;;;;;;;;;;;;19;;0,0,5;8;;;;; .\27a13690_hd\27a13690_manila.luac
-- Decompiled using luadec 0.7 by sztupy (http://winmo.sztupy.hu)
-- Command line was: -l ;;0,11,32;;0,17,38;;;;;;;5;;0,3,6,16,26;0,34;14,35;;;;;;;49,100,221;;0,0,223;;;;21,29;;;;;;;;;;;;;;;19;;0,0,5;8;;;;; .\27a13690_hd\27a13690_manila.luac

without the -l it outputs the decompile fine, but not as helpful.

Also, I tried it with the version 5 and it works fine with the -l option. So I re-downloaded version 6 tried it again and installed the version 7 both with the same results when it comes to the -l option.
[Windows Mobile Development and Hacking General] sztupy:
It looks like it crashed. Does it output anything with -d option set?
[Windows Mobile Development and Hacking General] smotrs:
That's what a crash looks like? Yeah, about 15K lines of info (see attached).
[Windows Mobile Development and Hacking General] sztupy:
Your LDS string is wrong somewhere. Unfortunatley when luadec crashes you can't use > to print its debug output to a file, because the last lines will be missing (which are the lines that have the information on where the problem might be), so can't find any useful information. Could you send me the luac file you're working on? I'll send you back the correct LDS
[Windows Mobile Development and Hacking General] smotrs:
Sure thing, there's 4 files,

27a13690_manila

27a13690_manila.luac

27a13690_manila.luac.dis

27a13690_manila.luac.guess​

does that mean a possible bug in the luadecguess or something else you think?
[Windows Mobile Development and Hacking General] sztupy:
It's not a bug, luadecguess can't determine the correct place of the locals. If it guesses wrong then it might make luadec crash, that's a known problem, and there is no workaround (well, except for knowing exactly where locals are defined and released... but if we knew this then decompiling would be much-much easier) So to correct this one has to look at the debug output and correct the LDS. This is the "find a smaller number" thing I was talking about earlier.

I attached the beta version of luadec 1.0 (the upcoming version) with an (incorrectly) decompiled file that has to be corrected. It also includes the LDS2 string I used.
[Windows Mobile Development and Hacking General] smotrs:
Well, with your tutorial I now understand what I have to do to decompile the scripts, HUGE help. Still trying to piece together the issue with the LDS, but it'll come I'm sure, just have to play with it more. Now I just have to figure out how to fix the decompiled code.

If I'm understanding it right, you should beable to (to a certain extent) recreate the function(s) within a LUA file just by looking at the disassembled output and piecing it back together. Looking at the first file within the play.zip you posted, I can see what the disassembled output is saying, but when I try to recreate the code based on what it says, the compare says I'm completely wrong.

For instance, this is what I see within the disassembly output for function 1 (or function 0 as it shows in the output),

1 [-]: GETGLOBAL R2 K0 ; R2 := _config_os
2 [-]: EQ 0 R2 K1 ; if R2 != "windowsmobile" then PC := 12
3 [-]: JMP 12
4 [-]: GETTABLE R2 R0 K2 ; R2 := R0["FilePath"]
5 [-]: GETTABLE R3 R1 K2 ; R3 := R1["FilePath"]
6 [-]: EQ 1 R2 R3 ; if R2 == R3 then PC := 9
7 [-]: JMP 9
8 [-]: LOADBOOL R2 0 1 ; R2 := false; PC := 10
9 [-]: LOADBOOL R2 1 0 ; R2 := true
10 [-]: RETURN R2 2 ; return R2
11 [-]: JMP 17 ; PC := 17
12 [-]: EQ 1 R0 R1 ; if R0 == R1 then PC := 15
13 [-]: JMP 15
14 [-]: LOADBOOL R2 0 1 ; R2 := false; PC := 16
15 [-]: LOADBOOL R2 1 0 ; R2 := true
16 [-]: RETURN R2 2 ; return R2
17 [-]: RETURN R0 1 ; return

Now, if I was to try to recreate what this says, this is what I believe it should look like,

RadioList_TestEqual = function(l_1_0, l_1_1)
local l_1_2 = _config_os
if l_1_2 == "windowsmobile" then
l_1_2 = l_1_0.FilePath
l_1_3 = l_1_1.FilePath
l_1_2 = l_1_2 == l_1_3
return l_1_2
elseif l_1_0 ~= l_1_1 then
l_1_2 = l_1_0 ~= l_1_1
return l_1_2
end
end

Problem is, compare mentions nothing about a R3 (l_1_3), it also says my line count and opcode count are WAY off.
[Windows Mobile Development and Hacking General] sztupy:
you can recreate a function but that's hard, and you should only resort to this when there is no other way :)

Your decompileation looks fine btw, but there are too much locals. R2 is not a local in this example (well, this example you posted me is not a straightforward one), correctly this should read:

RadioList_TestEqual = function(l_1_0, l_1_1)
if _config_os == "windowsmobile" then
return l_1_0.FilePath == l_1_1.FilePath
else
return l_1_0 == l_1_1
end
end

Lines like this:

8 [-]: LOADBOOL R2 0 1 ; R2 := false; PC := 10
9 [-]: LOADBOOL R2 1 0 ; R2 := true

always mean that conditional statements are assigned to a register, in this case R2
[Windows Mobile Development and Hacking General] smotrs:
Right, a LOADBOOL (boolean logic) returns a TRUE or FALSE, 1 or 0 type result. :)

That's how I would have created it if I was writing it from scratch, but the disassembly showed multiple locals. The first being the R2 and the second being the R3 that seemed to get created without a local call. I was thinking the idea was to try to match what the disassembly showed. The compare wasn't too bad, other then a few jmp differences. Which is why I tried to at least match the disassembly to show you what I mean.

BTW, this is what was created by luadec which is why I thought the locals were required, even if I wouldn't have normally used them. :D

RadioList_TestEqual = function(l_1_0, l_1_1)
local l_1_2 = _config_os
if l_1_2 == "windowsmobile" then
l_1_2 = l_1_0.FilePath
l_1_2 = l_1_2 == l_1_1.FilePath
return l_1_2
elseif l_1_0 ~= l_1_1 then
-- DECOMPILER ERROR: Attempted to build a boolean expression without a pending context

l_1_2 = error_maybe_false
return l_1_2
end
end

So what I'm gathering is, use the disassembly as a guide to recreating the code but don't use it verbatim as it results in badly written code (at least in my opinion).

BTW, that was the easier function, function 3 is harder and has even more compare differences. :rolleyes:
[Windows Mobile Development and Hacking General] sztupy:
sometimes you don't have to believe luadec / luadecguess :) You can always believe luadisasm! The fact that it says R2 and R3 is that the VM uses the 2nd and third register to store data, because it needs to use them to store data. This doesn't mean R2 or R3 are locals. But if at some point R2 is declared as local in the script you'll see, that only R3,R4, etc. are used mainly in the disassembly and R2 is usually left alone.
[Windows Mobile Development and Hacking General] smotrs:
Another question regarding compare this time. When you run compare and you see results like the following, what do they mean?

Function 3:
1> 1 [-]: GETTABLE R2 R0 K0 ; R2 := R0["Namespace"]
2> 1 [-]: GETTABLE R2 R0 K1 ; R2 := R0["Namespace"]
1> 2 [-]: SELF R2 R2 K1 ; R3 := R2; R2 := R2["FindName"]
2> 2 [-]: SELF R2 R2 K2 ; R3 := R2; R2 := R2["FindName"]
1> 3 [-]: LOADK R4 K2 ; R4 := "Text"
2> 3 [-]: LOADK R4 K3 ; R4 := "Text"

Other then the K0, K1, K2 and K3, they are the same, but obviously not the same. So what am I missing or not seeing?

Its a LOCAL, isn't it?
[Windows Mobile Development and Hacking General] sztupy:
No, it's a constant. Everything is a constant that is not a local :) For example in a line like Namespace.Findname("Text") Namespace, Findname and Text are all three constants. The number after the K represents which constant that char is. Constants are added by the lua compiler as they are encountered, so if you have K1 while the original has K0 that means that you have added something before the K1 in this example.

Usually this means missing locals as usual :) so if you have a line like

A=b.c

then refactor to

local x = b.c

A=x

this way "b" will be K0 and not "A"
[Windows Mobile Development and Hacking General] smotrs:
Constants huh?

When I originally ran the compare and got the results I pasted I had written the line like this,

l_4_2 = l_4_0.Namespace.Findname("Text")

I ended up fixing it by changing it to this,

local l_4_2 = l_4_0.Namespace.Findname("Text")

but didn't understand it, that's why I thought it was locals. :)

I think it makes sense now with your explanation.
[Windows Mobile Development and Hacking General] smotrs:
I'm so close on that last function in the 1f3be060_manila_1583A file from your play.zip other then a couple jump differences, everything matches except for a variable difference that I'm trying to resolve.

For instance,

1> 53 [-]: GETGLOBAL R3 K22 ; R3 := RadioButton_Initialize
2> 53 [-]: GETGLOBAL R4 K22 ; R4 := RadioButton_Initialize
1> 54 [-]: MOVE R4 R0 ; R4 := R0
2> 54 [-]: MOVE R5 R0 ; R5 := R0
1> 55 [-]: LOADBOOL R5 1 0 ; R5 := true
2> 55 [-]: LOADBOOL R6 1 0 ; R6 := true
1> 56 [-]: CALL R3 3 1 ; R3(R4,R5)
2> 56 [-]: CALL R4 3 1 ; R4(R5,R6)

Definitely not as quick as you, as I've been working on this function for about 2.5 hours, but I'm coming along I think. :)
[Windows Mobile Development and Hacking General] sztupy:
I think what I will say will freak you out: you have TOO MUCH locals declared :)
[Windows Mobile Development and Hacking General] smotrs:
Son of a ....

Well. I probably wouldn't have thought of that, so it's a good thing you mentioned it. Is it the fact that my version shows 1 more then the original version? Is that what the clue?
[Windows Mobile Development and Hacking General] sztupy:
yep. if your registers are ahead of the original than that means you have more locals than the original
[Windows Mobile Development and Hacking General] smotrs:
Ok, I finished the 1f3be060_manila but have a question before I post it. One of the lines in the disassembly shows this,

19 [-]: EQ 0 R3 K10 ; if R3 != 3.23791e-319 then PC := 41

should the code be written as

if bFromContactCard == 3.23791e-319 then

I don't think that's right though. What luadec tried to create and looks more logical was this,

if bFromContactCard == 1 then

Now if I compile my code with the 3.23791e-319 number and then disassemble it, the output looks like this,

19 [-]: EQ 0 R3 K10 ; if R3 != 0 then PC := 41

which leads me to think that maybe the number wasn't converted correctly or interpreted correctly by the disassembler.

I just can't imagine the 3.23... being correct though. The reason I bring this up is that there are a BUNCH of these in the 3e5b35f2_manila disassembly output.
[Windows Mobile Development and Hacking General] sztupy:
the disassembler was written for generic lua 5.1 scripts, but HTC uses the Q16.16 numeric format. The disassembler thinks it's a standard double and interprets it as is, this is why you'll see that 1 is actually 3.23791e-319 in the disassembly.
[Windows Mobile Development and Hacking General] 6Fg8:
I could change that in the conversion routines so that Q16.16 values are written as doubles in the ANSI converted output. Would be a more clean approach, but you would have to change the decompiler. What do you think?
[Windows Mobile Development and Hacking General] sztupy:
I don't think it's worth the effort. I already built the tools for Q16.16 (luac, lua and luadec), so if someone wants a disassembly he can use luac -l instead of luadisasm to get the values of the numeric constants
[Windows Mobile Development and Hacking General] smotrs:
Here's the 1f3be060_manila_1583A.
[Windows Mobile Development and Hacking General] sztupy:
double fun:

LuaDec 1.0 released

AND

All scripts decompiled, tested and ready to go!​

LuaDec 1.0

What's new:

Fixed OP_TFORLOOP

added support for LDS2 strings

added support to luac to print out the LDS2 string when compiling

LDS2 is like LDS, but with the added support to allow one to derefernce locals too. Luac with the -l2 option will output the LDS2 string that can be used to decompile the script if you happen to lose the source code. (you can't get the LDS2 string out from a -s compiled file, if you could do that life would be of course easier :) )

All scirpts decompiled

Packed them into an easy-to use form. Grab them from this site: http://winmo.sztupy.hu/manilakitchen.html

I also modified the Kitchen and started work on mode 9 converter
[Windows Mobile Development and Hacking General] smotrs:
Ok, so I take it you don't need the files in the play.zip decompiled anymore? :)

So now I'm playing with my 27a13690_manila file. I see a few things that are confusing though and now sure how they should be written.

The first is the following

15 [-]: LOADK R0 K7 ; R0 := -1.#QNAN
16 [-]: SETGLOBAL R0 K6 ; forecastMinutes := R0

what is the .#QNAN after the -1

The other issue is the following at opcode 186

156 [-]: GETGLOBAL R1 K11 ; R1 := _application
157 [-]: GETTABLE R1 R1 K19 ; R1 := R1["TouchFlo"]
158 [-]: SETTABLE R1 K20 K1 ; R1["HorizontalEnabled"] := 1
159 [-]: GETGLOBAL R1 K11 ; R1 := _application
160 [-]: GETTABLE R1 R1 K19 ; R1 := R1["TouchFlo"]
161 [-]: SETTABLE R1 K21 K1 ; R1["VerticalEnabled"] := 1
162 [-]: GETGLOBAL R1 K24 ; R1 := LastUpdateMinAgo
163 [-]: SETTABLE R1 K3 K4 ; R1["_visible"] := 0
164 [-]: GETGLOBAL R1 K11 ; R1 := _application
165 [-]: GETTABLE R1 R1 K25 ; R1 := R1["Navigation"]
166 [-]: SETTABLE R1 K26 K1 ; R1["TabVisibility"] := 1
167 [-]: LOADK R1 K66 ; R1 := "NoCities"
168 [-]: SETGLOBAL R1 K29 ; currentMode := R1
169 [-]: GETGLOBAL R1 K30 ; R1 := SetViewToGizmoMode
170 [-]: GETGLOBAL R2 K31 ; R2 := UpdateStatus
171 [-]: CALL R1 2 1 ; R1(R2)
172 [-]: GETGLOBAL R1 K11 ; R1 := _application
173 [-]: SELF R1 R1 K37 ; R2 := R1; R1 := R1["SetLeftSoftkey"]
174 [-]: GETGLOBAL R3 K38 ; R3 := Softkey
175 [-]: LOADK R4 K39 ; R4 := ""
176 [-]: LOADNIL R5 R5 ; R5 := nil
177 [-]: CALL R3 3 0 ; R3,... := R3(R4,R5)
178 [-]: CALL R1 0 1 ; R1(R2,...)
179 [-]: GETGLOBAL R1 K11 ; R1 := _application
180 [-]: SELF R1 R1 K40 ; R2 := R1; R1 := R1["SetRightSoftkey"]
181 [-]: GETGLOBAL R3 K38 ; R3 := Softkey
182 [-]: GETGLOBAL R4 K41 ; R4 := Locale
183 [-]: SELF R4 R4 K42 ; R5 := R4; R4 := R4["GetString"]
184 [-]: LOADK R6 K67 ; R6 := "IDS_ADDCITY"
185 [-]: CALL R4 3 2 ; R4 := R4(R5,R6)
186 [-]: CLOSURE R5 0 ; R5 := closure(Function #16.0)
187 [-]: CALL R3 3 0 ; R3,... := R3(R4,R5)
188 [-]: CALL R1 0 1 ; R1(R2,...)
189 [-]: GETGLOBAL R1 K7 ; R1 := ProblemGroup
190 [-]: SETTABLE R1 K3 K1 ; R1["_visible"] := 1
191 [-]: GETGLOBAL R1 K68 ; R1 := ProblemText
192 [-]: SETTABLE R1 K69 K70 ; R1["String"] := "[[IDS_ADDCITY]]"
193 [-]: JMP 301 ; PC := 301

the output from luadec looks like this, but I have no idea what the closure line is doing in there.

_application.TouchFlo.HorizontalEnabled = true
_application.TouchFlo.VerticalEnabled = true
LastUpdateMinAgo._visible = false
_application.Navigation.TabVisibility = true
currentMode = "NoCities"
SetViewToGizmoMode(UpdateStatus)
_application:SetLeftSoftkey(Softkey("", nil))
_application:SetRightSoftkey(Softkey(Locale:GetString("IDS_ADDCITY"), function()
SwitchMode("CityPicker")
end))
ProblemGroup._visible = true
ProblemText.String = "[[IDS_ADDCITY]]"

I think something got messed up somewhere as it looks like a remnant of a previous code snippet.

I also have a request, how about a quick converter to convert to and from Q16.16 or maybe you can explain the luac -l a little more as I didn't understand your explanation earlier?