1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
namespace AdamsToolkit.Core;
/// <summary>
/// Localiza a instalação do FiveM no PC de cada pessoa (nem todos usam o caminho
/// default): 1) protocol handler fivem:// no registry, 2) chave de uninstall,
/// 3) %LOCALAPPDATA%\FiveM. Nunca assumir caminhos fixos fora daqui.
/// </summary>
public static class FiveMLocator
{
/// <summary>Pasta que contém o FiveM.exe (ex: C:\Users\x\AppData\Local\FiveM), ou null.</summary>
public static string? RootDir
{
get
{
// 1. protocol handler fivem:// — aponta para o FiveM.exe real desta máquina
foreach (var keyPath in new[]
{
@"Software\Classes\FiveM.ProtocolHandler\shell\open\command",
@"Software\Classes\fivem\shell\open\command",
})
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(keyPath);
if (k?.GetValue("") is string cmd)
{
var exe = ParseExePath(cmd);
if (exe != null && File.Exists(exe))
return Path.GetDirectoryName(exe);
}
}
catch { }
}
// 2. entrada de uninstall
try
{
using var k = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Uninstall\FiveM");
if (k?.GetValue("InstallLocation") is string loc &&
loc.Length > 0 && Directory.Exists(loc))
return loc.TrimEnd('\\');
}
catch { }
// 3. caminho default
var def = Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%\\FiveM");
return Directory.Exists(def) ? def : null;
}
}
public static string? ExePath
{
get
{
var root = RootDir;
if (root == null) return null;
var exe = Path.Combine(root, "FiveM.exe");
return File.Exists(exe) ? exe : null;
}
}
/// <summary>Pasta "FiveM Application Data" (FiveM.app), ou null.</summary>
public static string? AppDataDir
{
get
{
var root = RootDir;
if (root == null) return null;
var app = Path.Combine(root, "FiveM.app");
if (Directory.Exists(app)) return app;
// instalações onde o utilizador apontou diretamente para dentro do FiveM.app
return File.Exists(Path.Combine(root, "CitizenFX.ini")) ? root : null;
}
}
public static bool IsFiveMRunning()
{
try
{
return Process.GetProcesses().Any(p =>
{
try { return p.ProcessName.StartsWith("FiveM", StringComparison.OrdinalIgnoreCase); }
catch { return false; }
});
}
catch { return false; }
}
private static string? ParseExePath(string command)
{
command = command.Trim();
if (command.StartsWith('"'))
{
var end = command.IndexOf('"', 1);
return end > 1 ? command[1..end] : null;
}
var sp = command.IndexOf(' ');
return sp > 0 ? command[..sp] : command;
}
// ===== limpeza de cache =====
/// <summary>
/// Apaga todas as pastas dentro de FiveM.app\data EXCETO game-storage
/// (guarda os dados persistentes do jogo). Devolve (sucesso, mensagem).
/// </summary>
public static (bool ok, string message) ClearCache()
{
var app = AppDataDir;
if (app == null)
return (false, "Pasta do FiveM não encontrada neste PC.");
var data = Path.Combine(app, "data");
if (!Directory.Exists(data))
return (false, "Pasta de cache (data) não existe — nada para limpar.");
if (IsFiveMRunning())
return (false, "O FiveM está aberto — fecha-o primeiro e tenta de novo.");
long freed = 0;
var removed = 0;
var failed = 0;
foreach (var dir in Directory.GetDirectories(data))
{
if (Path.GetFileName(dir).Equals("game-storage", StringComparison.OrdinalIgnoreCase))
continue;
try
{
freed += DirSize(dir);
Directory.Delete(dir, true);
removed++;
}
catch { failed++; }
}
if (removed == 0 && failed == 0)
return (true, "Cache já estava limpa.");
var msg = $"Cache limpa: {removed} pasta(s), {freed / 1048576.0:0} MB libertados. game-storage preservada.";
if (failed > 0) msg += $" ({failed} pasta(s) bloqueadas ficaram por apagar.)";
return (true, msg);
}
private static long DirSize(string dir)
{
long size = 0;
try
{
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
{
try { size += new FileInfo(f).Length; } catch { }
}
}
catch { }
return size;
}
}