@@ -35,6 +35,10 @@ local recentWrites = {}
3535-- Cache de gravabilidade por resource (nome -> bool). Escrita cross-resource e
3636-- bloqueada pelo sandbox do FiveM, exceto com add_filesystem_permission.
3737local resourceWritable = {}
38+ -- Definidas mais abaixo; forward-declaradas porque isResourceWritable (acima do
39+ -- ponto de definicao) precisa usar attemptWrite.
40+ local writtenMatches
41+ local attemptWrite
3842
3943local function trim (value )
4044 return tostring (value or ' ' ):gsub (' ^%s+' , ' ' ):gsub (' %s+$' , ' ' )
@@ -518,17 +522,22 @@ local function isResourceWritable(target)
518522 return cached
519523 end
520524
521- local probeRel = ' .qadmin_keep'
525+ -- Probe com o MESMO mecanismo do save real (native + fallback os.execute),
526+ -- escrevendo o marcador oculto .qadmin_keep com um token e relendo.
527+ local probeAbs = target .root .. ' \\ .qadmin_keep'
528+ local probeTarget = {
529+ resource = target .resource ,
530+ relative = ' .qadmin_keep' ,
531+ absolute = probeAbs ,
532+ root = target .root ,
533+ }
522534 local token = (' mriqadmin-%s' ):format (tostring (os.time ()))
523- SaveResourceFile (target .resource , probeRel , token , # token )
524- local back = LoadResourceFile (target .resource , probeRel )
525- local writable = type (back ) == ' string' and back == token
535+ local writable = attemptWrite (probeTarget , token )
526536
527537 if writable then
528538 -- limpa o probe (best-effort; se nao der, fica oculto nas listagens).
529- local probeAbs = target .root .. ' \\ ' .. probeRel
530539 if not os.remove (probeAbs ) and pathExists (probeAbs ) then
531- SaveResourceFile (target .resource , probeRel , ' ' , 0 )
540+ SaveResourceFile (target .resource , ' .qadmin_keep ' , ' ' , 0 )
532541 end
533542 end
534543
691700-- Confirma a gravacao relendo o conteudo do disco (io cru) ou via native
692701-- (LoadResourceFile). A leitura e a fonte da verdade — o retorno de write/close
693702-- do Lua do FiveM nao e confiavel.
694- local function writtenMatches (target , text )
703+ writtenMatches = function (target , text )
695704 local disk = readPhysicalFile (target .absolute )
696705 if type (disk ) == ' string' and sameTextContent (disk , text ) then
697706 return true
@@ -701,19 +710,49 @@ local function writtenMatches(target, text)
701710 return type (native ) == ' string' and sameTextContent (native , text )
702711end
703712
713+ -- Grava `text` no destino e confirma por releitura. Retorna true se gravou.
714+ -- 1) native SaveResourceFile — limpa; bloqueada cross-resource pelo sandbox.
715+ -- 2) fallback os.execute (copy/cp): escreve um temp DENTRO do mri_Qadmin (que
716+ -- e sempre gravavel) e copia via shell para o destino. O processo externo
717+ -- do shell NAO passa pela vfs do FiveM, contornando o sandbox.
718+ attemptWrite = function (target , text )
719+ SaveResourceFile (target .resource , target .relative , text , # text )
720+ if writtenMatches (target , text ) then
721+ return true
722+ end
723+
724+ -- Caminhos com metacaracteres de shell nao vao pro os.execute.
725+ if hasShellHazard (target .absolute ) then
726+ return false
727+ end
728+
729+ local tmpRel = ' .qadmin_write_tmp'
730+ SaveResourceFile (GetCurrentResourceName (), tmpRel , text , # text )
731+ local tmpAbs = normalizeSlashes (GetResourcePath (GetCurrentResourceName ()) or ' ' ) .. ' \\ ' .. tmpRel
732+ if hasShellHazard (tmpAbs ) then
733+ os.remove (tmpAbs )
734+ return false
735+ end
736+
737+ local command = isWindows ()
738+ and (' cmd /c copy /y %s %s >nul 2>nul' ):format (cmdQuote (tmpAbs ), cmdQuote (target .absolute ))
739+ or (' cp -f %s %s >/dev/null 2>&1' ):format (cmdQuote (tmpAbs ), cmdQuote (target .absolute ))
740+ os.execute (command )
741+ os.remove (tmpAbs )
742+
743+ return writtenMatches (target , text )
744+ end
745+
704746local function writeDiskFile (target , content )
705747 local text = tostring (content or ' ' )
706748 -- best-effort: garante a pasta pai (no-op se ja existir).
707749 ensureDirectoryExists (target .absolute :match (' ^(.*)[\\ /][^\\ /]+$' ) or normalizeSlashes (target .root ))
708750
709- -- Usa a native SaveResourceFile (grava relativo ao resource). Importante: NAO
710- -- usar io.open('wb') no caminho cru — em artifacts com sandbox o write e
711- -- bloqueado, mas o 'wb' ja TRUNCOU o arquivo na abertura (perda de dados). A
712- -- native respeita o sandbox de forma limpa, sem tocar o arquivo quando barra.
713- -- Para o proprio mri_Qadmin sempre funciona; para outros resources depende de
714- -- add_filesystem_permission no server.cfg.
715- SaveResourceFile (target .resource , target .relative , text , # text )
716- if writtenMatches (target , text ) then
751+ -- attemptWrite: native SaveResourceFile e, se o sandbox barrar, fallback via
752+ -- os.execute (copy do temp). Importante: NUNCA io.open('wb') no caminho cru —
753+ -- com sandbox o write e bloqueado mas o 'wb' ja TRUNCOU o arquivo (perda de
754+ -- dados).
755+ if attemptWrite (target , text ) then
717756 recentWrites [targetKey (target )] = text
718757 resourceWritable [target .resource ] = true
719758 return true
0 commit comments