 |
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
paul44 Expert Cheater
Reputation: 2
Joined: 20 Jul 2017 Posts: 194
|
Posted: Fri Nov 15, 2024 5:39 am Post subject: run lua function async using Form button [Solved] |
|
|
Via the addresslist, one can enable the 'execute asynchronous' option. This offers - at least - 3 important features:
1. CE will not "hang" during its running
2. info updated to a form (gui) will constantly be updated
3. a 'watch' icon visually shows its progress state
How can one configure/program this using lua ?
Basically: i have a form with a button, to run a particular script. If i run the script via the addresslist, everything is peachy (as per aforementioned). If i try to run this same script via the (form) button, no cigar... (ie it does execute, but CE shows "no activity", and trying to 'intervene' prompts "it being busy")
btw: if i disable the 'async' option in the addresslist, i "loose" that same featureset.
ps: createthread comes to mind, but not overly enthousiastic about using it (as no real experience with its use)
ps2: also checked celua.txt but nothing seems "appropriate"
Last edited by paul44 on Thu Nov 21, 2024 11:50 am; edited 3 times in total |
|
Back to top |
|
 |
panraven Grandmaster Cheater
Reputation: 61
Joined: 01 Oct 2008 Posts: 958
|
Posted: Fri Nov 15, 2024 10:23 am Post subject: |
|
|
Here an example to assure a memrec to be exec async, (copy and paste in the addresslist panel)
Code: |
<?xml version="1.0" encoding="utf-8"?>
<CheatTable>
<CheatEntries>
<CheatEntry>
<ID>0</ID>
<Description>"test async exec"</Description>
<VariableType>Auto Assembler Script</VariableType>
<AssemblerScript Async="1">{$lua}
-- aa command for testing
local cmd = 'log'
unregisterAutoAssemblerCommand(cmd)
registerAutoAssemblerCommand(cmd, function(msg, sc)
print((sc and '[s]'or'[x]')..msg)
end)
cmd = 'nap'
unregisterAutoAssemblerCommand(cmd)
registerAutoAssemblerCommand(cmd, function(ms, sc)
if not sc then
ms = tonumber(ms) or 1
sleep(ms)
end
end)
--
{$asm}
{$lua}
-- code to asure async exec
if syntaxcheck then return end
local mr = memrec
if not mr.Async then
synchronize(createTimer, 100, function()
mr.Async = true
mr.Active = not mr.Active
end)
error're-activating' -- this will stop current exec
end
--
{$asm}
[ENABLE]
log(hello)
nap(3000)
log(world)
nap(3000)
log(the end)
[DISABLE]
</AssemblerScript>
</CheatEntry>
</CheatEntries>
</CheatTable>
|
lua code in an async exec mr that use UI should use synchronize (send the call to main thread, the above createTimer is an example )
_________________
- Retarded. |
|
Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 150
Joined: 06 Jul 2014 Posts: 4657
|
Posted: Fri Nov 15, 2024 11:33 am Post subject: |
|
|
All {$lua} blocks are executed in a separate thread when memrec.Async is set to true. Use createThread if needed.
This also means you should never access anything related to the GUI from that {$lua} block of code. Only the main thread should access the GUI. This includes but is not limited to GUI elements (buttons, edits, menus), the address list / memory records, and timers. If you need to access any of these, use synchronize.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
 |
paul44 Expert Cheater
Reputation: 2
Joined: 20 Jul 2017 Posts: 194
|
Posted: Fri Nov 15, 2024 12:53 pm Post subject: strictly lua... |
|
|
^^ @panraven: i now realise that my wording is incorrect here. I am actually talking about Lua only; iow no use/call of a script in the addresslist. And I should have stated 'function' instead of 'script' ! My bad...
Picture this: you have a form with 2 elements: 1 button and 1 label (or listbox, etc). when clicking that button, it will/must run a lua functon (~ not script) - using eg 'Onclick' event - which will (constantly) be updating that label... (assume the label is a counter)
=> what happens is that you will not see the label being updated ("counting") till it finishes (which basically will show you the endresult)...
==> and if you touch any CE window (incl form), it'll seem to "hang"... till the function finishes of course. Which is the 2nd reason why having the function/script running async !
And as #ParkourPenguin states: gui updates must be executed using 'synchronize()'; as how i have set it. But based on my observation, synchronize will only "behave" as such if/when the function (or script) is set in a async state (~ if i'm wording this correctly)
If you need a small example table/form, let me know.
|
|
Back to top |
|
 |
panraven Grandmaster Cheater
Reputation: 61
Joined: 01 Oct 2008 Posts: 958
|
Posted: Fri Nov 15, 2024 3:23 pm Post subject: |
|
|
Usually event handler should be run short, as if it not exit the handling function, it may block other event to be handle.
If your handler may start a long processing, it is better do it in a timer (if it is repeating, or can be simulate as a state machine), or spawn an os thread.
Not sure if following can illustrate the point or match your problem, but hope it can be useful.
It use a module from my github which need internet access.
Code: |
if not CEO then
CEO = GetInternet().GetURL'https://raw.githubusercontent.com/wolfoops/XKCE/master/ceo.lua'
CEO = CEO and #CEO > 200 and load(CEO)
CEO = CEO and CEO()
end
local UI = CEO and CEO.ceo_upd
if not UI then error'CEO not load'end
-- test form
local frm = UI(createForm()){
Caption ='CntTest', Name='CntTest', OnClose = function() return caFree end,
Constraints = { minWidth = 300, minHeight = 200 }, BorderStyle= bsSizeable,
Button{ Name ='btnTimer', Caption='Timer', Top = 0, AutoSize = true},
Button{ Name ='btnLongSleep', Caption='Loop Sleep Long', Top = 30, AutoSize = true} ,
Button{ Name ='btnShortSleep', Caption='Loop Sleep Short', Top = 60, AutoSize = true},
Button{ Name ='btnStep', Caption='Step and stop others ', Top = 90, AutoSize = true},
Label{ Name ='lbRun', Caption='IDLE', Top = 0, Left = 200, AutoSize = true},
Label{ Name ='lbCnt', Caption='#Count', Top = 30, Left = 200, AutoSize = true},
Label{ Name ='otherCnt', Caption='#other cnt', Top = 60, Left = 200, AutoSize = true},
}
frm.Show()
-- some UI helper
local function inc(o)
return function(dir)
dir = tonumber(dir) or 1
local caption = o.Caption
local cnt = tonumber(caption) or 0
synchronize(o.SetCaption,(cnt + dir) % 100)
end
end
local function runMsg(run)
synchronize(frm.lbRun.SetCaption, run and tostring(run) or '-oops-')
end
local ocnt = frm.otherCnt
local incOther = inc(ocnt)
local cnt = frm.lbCnt
local incCounter = inc(cnt)
-- the constant running counter
local otmr = createTimer(ocnt)
otmr.Interval, otmr.OnTimer = 50, incOther
----
-- state of other button handlers
local stopLong, stopShort, bTimer
-- use to stop other hanlders
local function stopOthers()
runLong, runShort = false, false
if bTimer then bTimer = nil, synchronize(bTimer.Destroy); return true end
end
-- handlers
-- btnStep
local step = frm.btnStep
step.OnClick = function(me)
runMsg'Step'
stopOthers()
incCounter(-1)
end
-- btnTimer
local timer = frm.btnTimer
timer.OnClick = function(me)
if stopOthers()then return end
runMsg'Timer'
bTimer = createTimer(me)
bTimer.Interval, bTimer.OnTimer = 1000, incCounter
end
-- btnLongSleep (large ms)
local long = frm.btnLongSleep
local maxLongIter = 10
long.OnClick = function(me)
if runLong then return end
stopOthers()
runLong = true
local iter = 0
runMsg'long'
createThread(function(thd)
while runLong and iter < maxLongIter do
iter = iter + 1
incCounter()
sleep(1000)
end
runLong = false
runMsg'IDLE'
end)
end
-- btnShortSleep (small ms)
local short = frm.btnShortSleep
local maxShortIter = 200
short.OnClick = function(me)
if runShort then return end
stopOthers()
runShort = true
local iter = 0
runMsg'Short'
createThread(function(thd)
while runShort and iter < maxShortIter do
iter = iter + 1
if (iter % 5) == 0 then incCounter(-1)end
sleep(50)
end
runShort = false
runMsg'IDLE'
end)
end
|
_________________
- Retarded. |
|
Back to top |
|
 |
paul44 Expert Cheater
Reputation: 2
Joined: 20 Jul 2017 Posts: 194
|
Posted: Sat Nov 16, 2024 2:51 am Post subject: createthread seems the proper solution... |
|
|
^ @panraven: "Usually event handler should be run short, as if it not exit the handling function, it may block other event to be handle".
=> which is indeed my biggest concern here; and i might come back on this in due time...
that said, in the meantime - and I had not read your response here - i had found this: [ https://www.cheatengine.org/forum/viewtopic.php?t=611040&sid=a123ea47687796ba46e7a1a85a5d490c ]; and have used that in my call script. And it now works as expected !
=> he did not mention the 'terminate()' method, which must be added (see below)
BIG question: is there a way to see if (particular/initiated) threads are effectively terminated ? Perhaps use a technique used here for timers: [ https://www.cheatengine.org/forum/viewtopic.php?t=618933&sid=ea8d85619a9513450cc63fbe2f1a3443 ] ?!
(which would allow one to kill the thread, if needed)
=> Btw: i ran [ms process explorer] to see when/where the thread would be added/removed, but it did not show any "particular/related" update...?
PS: consider the topic as closed; will leave it open for a couple of days for possible feedback...
[code] createThread(function (threadObj)
-- Loop until the thread is terminated..
while (not threadObj.Terminated) do
-- Sleep to prevent excessive CPU usage..
Sleep(100)
asmScan(0)
... (any other fn that need to run here)
analyse_Offset()
threadObj.terminate()
end
end)
[/code]
Last edited by paul44 on Sun Nov 17, 2024 4:16 am; edited 1 time in total |
|
Back to top |
|
 |
Dark Byte Site Admin
Reputation: 468
Joined: 09 May 2003 Posts: 25709 Location: The netherlands
|
Posted: Sat Nov 16, 2024 3:22 am Post subject: |
|
|
normally created threads will free themselves on return/exit/error unless you add the code threadobject.freeOnTerminate(false)
if you want to see if a thread has finished and it doesn't free itself on terminate, then you can check threadobject.Finished
You will have to call threadobject.destroy() when done.
Note that calling threadobject.destroy() will set Terminated to true and then wait until Finished is set, so you can also do that if you only need to know when it's done and then discard it.
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 35
Joined: 16 Feb 2017 Posts: 1483
|
Posted: Sat Nov 16, 2024 7:04 am Post subject: |
|
|
(Off-topic)
Code: | if not CEO then
CEO = GetInternet().GetURL'https://raw.githubusercontent.com/wolfoops/XKCE/master/ceo.lua'
CEO = CEO and #CEO > 200 and load(CEO)
CEO = CEO and CEO()
end
local UI = CEO and CEO.ceo_upd
if not UI then error'CEO not load'end
-- test form
local frm = UI(createForm()){
Caption ='CntTest', Name='CntTest', OnClose = function() return caFree end,
Constraints = { minWidth = 300, minHeight = 200 }, BorderStyle= bsSizeable,
Button{ Name ='btnTimer', Caption='Timer', Top = 0, AutoSize = true},
Button{ Name ='btnLongSleep', Caption='Loop Sleep Long', Top = 30, AutoSize = true} ,
Button{ Name ='btnShortSleep', Caption='Loop Sleep Short', Top = 60, AutoSize = true},
Button{ Name ='btnStep', Caption='Step and stop others ', Top = 90, AutoSize = true},
Label{ Name ='lbRun', Caption='IDLE', Top = 0, Left = 200, AutoSize = true},
Label{ Name ='lbCnt', Caption='#Count', Top = 30, Left = 200, AutoSize = true},
Label{ Name ='otherCnt', Caption='#other cnt', Top = 60, Left = 200, AutoSize = true},
}
frm.Show() |
Am I the only one noticing these?
@panraven is doing something and we are left unfamiliar with CE.
Code: | aa2 = "prt"
load("return registerLuaFunctionHighlight(aa2)")()
function prt(s) print(s) end
prt("hello lua")
-- After running the code, click on the prt text above and
-- create a prt example below. prt("my prt") |
Thanks for the beautiful design examples @panraven.
_________________
|
|
Back to top |
|
 |
paul44 Expert Cheater
Reputation: 2
Joined: 20 Jul 2017 Posts: 194
|
Posted: Sun Nov 17, 2024 4:38 am Post subject: feedback & update |
|
|
^^ @Dark Byte: yep, i already noticed that. In fact, i tried to print the name of the thread, and got a nil error after the 'terminate' and outside the 'createthread'.
also came across this: [ https://www.cheatengine.org/forum/viewtopic.php?p=5743663&sid=b3cf17aae897feffd573f882ae3ce8d7 ]... Perfect example to get a pretty good idea on how to tackle gui updates.
=> still: I do get the 'thread' concept, but have to admit i hardly have "grip" on its implementation. In snippet mentioned above, my logic tells me that it will constantly (?) execute them functions till it encounters "a" terminate somehow. But: if it just/always executes the 3 fn - in that order - then why the loop ?
(I think i've tested this without the loop and then my 3 fn got executed instantly, rather then consecutively - or something of that order)
==> anyways: it is working fine now from within the form; so happy bunny...
^ @AylinCE: i actually tried out his example this morning, but ran into a 'Lua Panic' error. since this is not "directly" related to this topic, i've sent him a pm to tackle tihs offline as such...
Minor detail, but really not all that important: can the (animated gif ?) "clock_icon" - as shown in the addresslist - also be used on a form ?
(i'm assuming here it is/loaded_up_as part of CE)
|
|
Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 35
Joined: 16 Feb 2017 Posts: 1483
|
Posted: Sun Nov 17, 2024 12:26 pm Post subject: Re: feedback & update |
|
|
paul44 wrote: | Minor detail, but really not all that important: can the (animated gif ?) "clock_icon" - as shown in the addresslist - also be used on a form ?
(i'm assuming here it is/loaded_up_as part of CE) |
What if I said I've never seen it? It could be a feature I haven't used or come across.
(Maybe @Dark Byte has a comment on this.)
Here are some different ideas:
--( image result code: "Ekr1")
Code: | local tp3 = 10
local lf3 = 10
if mf2 then mf2.Destroy() mf2=nil end
mf2 = createForm()
mf2.Caption="test"
local spbtn = {}
for i=0, 40 do
spbtn["btn"..i] = createComponentClass('TSpeedButton', mf2)
spbtn["btn"..i].Parent = mf2
spbtn["btn"..i].Images = MainForm.mfImageList
spbtn["btn"..i].ImageIndex = i
spbtn["btn"..i].Height=25
spbtn["btn"..i].Width=25
spbtn["btn"..i].Left=lf3
spbtn["btn"..i].Top=tp3
spbtn["btn"..i].OnClick=function() print("Image Index: " .. i) end
lf3=tonumber(lf3) + 35
--print(i .. " - " .. lf3)
if lf3==360 then lf3=10 tp3=tonumber(tp3) + 35 end
end
spbtn.bckpnl1 = createPanel(mf2)
spbtn.bckpnl1.Height=35
spbtn.bckpnl1.Width=35
spbtn.bckpnl1.Left=10
spbtn.bckpnl1.Top=180
spbtn.btn7.Parent=spbtn.bckpnl1
spbtn.btn7.Left=5
spbtn.btn7.Top=5
if gftmr1 then gftmr1.Destroy() gftmr1=nil end
gftmr1=createTimer()
gftmr1.Interval=1000
gftmr1.Enabled=false
gftmr1.OnTimer=function()
spbtn.bckpnl1.Color = (spbtn.bckpnl1.Color + 100001)
end
mf2.OnClick = function() -- form click..
if gftmr1.Enabled==false then
gftmr1.Enabled=true
else
gftmr1.Enabled=false
end
end |
-- or gift: --( image result code: "Ekr2")
Code: | if fr3 then fr3.Destroy() fr3=nil end
fr3=createForm()
fr3.Caption="gif test"
img3=createImage(fr3)
img3.Height=100
img3.Width=100
img3.Left=30 img3.Top=50
img3.Stretch=true
pnl3=createPanel(fr3)
pnl3.Height=110
pnl3.Width=110
pnl3.Left=165 pnl3.Top=45
img4=createImage(pnl3)
img4.Height=100
img4.Width=100
img4.Left=5 img4.Top=2
img4.Stretch=true
if loadTimer then loadTimer.Destroy() loadTimer=nil end
loadTimer=createTimer() loadTimer.Interval=1000 / 6 loadTimer.Enabled=false
function createPictureFromURL(url)
http = getInternet()
file = http.getURL(url)
http.destroy()
pct = createPicture()
stream = createStringStream(file)
pct.loadFromStream(stream)
return pct
end
local picTbl3 = {}
local lnk1 = [[https://i.hizliresim.com/]]
local linkTbl3 = {"ritk1xn.png","p7tdfjo.png",
"bmrvmy3.png","luxigeo.png",
"gku655w.png","2kznd0u.png",
"1pncm56.png","dld3qyg.png",
"alszbml.png","796ljmf.png",
"ssoxcaf.png","nssydd3.png"}
for i=1, 12 do -- my gif picture max 12..
local pct1 = createPictureFromURL(lnk1..linkTbl3[i])
table.insert(picTbl3, pct1)
end
img3.Picture = picTbl3[1]
img4.Picture = picTbl3[1]
local indx = 0
local img3start = 0
local img4start = 0
loadTimer.OnTimer=function()
if img3start==1 then
indx=tonumber(indx) + 1
if indx==13 then indx=1 end
img3.Picture = picTbl3[indx]
end
if img4start==1 then
pnl3.Color = (pnl3.Color + 100001)
end
if img3start==0 and img4start==0 then
loadTimer.Enabled=false
end
end
img3.OnClick=function()
if img3start==0 then
img3start=1
loadTimer.Enabled=true
else
img3start=0
img3.Picture = picTbl3[1]
end
end
img4.OnClick=function()
if img4start==0 then
img4start=1
loadTimer.Enabled=true
else
img4start=0
end
end |
Description: |
|
Filesize: |
6.75 KB |
Viewed: |
8539 Time(s) |

|
Description: |
|
Filesize: |
14.89 KB |
Viewed: |
8539 Time(s) |

|
_________________
Last edited by AylinCE on Mon Nov 18, 2024 1:38 pm; edited 1 time in total |
|
Back to top |
|
 |
Dark Byte Site Admin
Reputation: 468
Joined: 09 May 2003 Posts: 25709 Location: The netherlands
|
Posted: Sun Nov 17, 2024 2:54 pm Post subject: |
|
|
the clock is just a canvas draw. A circle, and two lines drawn from the center to a x and y calculated using cos and sin
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 35
Joined: 16 Feb 2017 Posts: 1483
|
Posted: Mon Nov 18, 2024 10:38 am Post subject: |
|
|
It's a bit of an old idea.
If it's edited, it'll make for a shorter and better code.
I think it'll give you an idea to start with.
Code: | function ClockMaker(f, height1, width1, leftX, topY)
if item then item.destroy() item = nil end
item = createImage(f)
item.Height=height1
item.Width=width1
item.Left=leftX
item.Top=topY
item.Stretch = true
local x0=tonumber(item.width / 2)
local y0=tonumber(item.height / 2)
local x10=tonumber(item.width / 2)
local y10=tonumber(item.height / 2)
local x20=tonumber(item.width / 2)
local y20=tonumber(item.height / 2)
local xx=tonumber(item.width / 2)
local r = y0 - 20
local x = x0
local x1 = x10
local x2 = x20
local xx1=item.Width/6
------------------------------------
if ix3 then ix3.destroy() ix3 = nil end
ix3 = createLabel(f)
ix3.Font.Size = item.Width / 12
ix3.Font.Style = "fsBold"
ix3.Font.Color = "0x00FF00"
ix3.AutoSize = false
ix3.Alignment= "taCenter"
ix3.Height = item.Height / 8
ix3.Width = item.Width / 2
ix3.Left = item.Left + (item.Width / 2) - (ix3.Width / 2)
ix3.Top = item.Top + item.Height / 4
local s = tonumber(os.date('%S'))
local m2 = tonumber(os.date('%M')) * 60
local h2 = tonumber(os.date('%I')) * 3600
--print(h2.." - "..m2.." - "..s)
local m = tonumber(os.date('%M')) local h = tonumber(os.date('%I'))
-------------------------------------
function test(timer)
local x,y = item.screenToClient(x,y)
local bm=item.picture.Bitmap
bm.Width=item.Width
bm.Height=item.Height
local Canvas = bm.Canvas
Canvas.clear()
bm.Canvas.Brush.Color="0x2000a0";
bm.Canvas.roundRect(0, 0, item.Height, item.Width, item.Height, item.Width);
r = math.random(x0)
y = y0 - r
if r < xx then r=xx end
s = s + 1
x = x0 + math.floor(r * math.sin(s * math.pi/30))
y = y0 - math.floor(r * math.cos(s * math.pi/30))
r = math.random(x10)
if r then r=xx - 15 end
local x1,y1 = item.screenToClient(x1,y1)
y1 = y10 - r
m2 = m2 + 1
x1 = x10 + math.floor(r * math.sin(m2 * math.pi/1800))
y1 = y10 - math.floor(r * math.cos(m2 * math.pi/1800))
if r then r=xx - 40 end
local x2,y2 = item.screenToClient(x2,y2)
y2 = y20 - r
h2 = h2 + 1
x2 = x20 + math.floor(r * math.sin(h2 * math.pi/21000))
y2 = y20 - math.floor(r * math.cos(h2 * math.pi/21000))
Canvas.pen.Color = "0xFFFFFF"
Canvas.pen.Width = 2
Canvas.line(x, y, xx, xx) --s
Canvas.pen.Color = "0xFFFF00"
Canvas.pen.Width = 4
Canvas.line(x1, y1, xx, xx) --d
Canvas.pen.Color = "0x00ffff"
Canvas.pen.Width = 6
Canvas.line(x2, y2 + 10, xx, xx) --h
Canvas.pen.Color = "0xFF9000"
Canvas.pen.Width = 3
Canvas.line(xx1*6, xx1*3, xx1*5, xx1*3) --15 >> 3
Canvas.line(xx1*3, xx1*6, xx1*3, xx1*5) --30 >> 6
Canvas.line(0, xx1*3, xx1, xx1*3) --45 >> 9
Canvas.line(xx1*3, xx1, xx1*3, 0) --60 >> 0-12
bm.TransparentColor=clBlack
item.Transparent=true
if s >= 60 then s=0 m=m + 1 end
if m >= 60 then m=0 h=h + 1 end
if h >= 13 then h=1 end
------------------------------------
ix3.caption=(h..":"..m..":"..s)
ix3.bringToFront()
end
if t then t.destroy(); t=nil end
t=createTimer()
t.Interval = 1000
t.onTimer = test
end
--use of
--ClockMaker(f, height1, width1, leftX, topY)
--form = you Trainer name
ClockMaker(UDF1, 100, 100, 75, 50) |
Description: |
|
Filesize: |
4.39 KB |
Viewed: |
8426 Time(s) |

|
_________________
|
|
Back to top |
|
 |
paul44 Expert Cheater
Reputation: 2
Joined: 20 Jul 2017 Posts: 194
|
Posted: Mon Nov 18, 2024 12:16 pm Post subject: "thread" animation |
|
|
^^ @AylinCE: "What if I said I've never seen it?"
see here [ https://ibb.co/jzzmz1k ]
=> i use/set this in particular when "seeding" lots of info (such as inventory) to the listbox. and: it is not just important to the user - who now visually sees it is still "in progress" - but also to me on development/testing end !
As for your examples/suggestions: I have quite a few things to do this week, but surely have a look by next weekend latest. If i'm "inspired", i will definitely get back to you... (via pm ~ if active @FRF, do pm me from there)
@Dark Byte: thx for that "mystery". one can probably find that code on github then.
|
|
Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|