DEADSOFTWARE

game: do not use absolute path in wad selection widgets (it looks ugly, and [almost...
[d2df-sdl.git] / src / engine / e_res.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$I ../shared/a_modes.inc}
16 unit e_res;
18 interface
20 uses SysUtils, Utils, Classes;
22 var
23 debug_e_res: Boolean;
25 {-------------------------------------------}
26 {--- insert separator beetwin a and b ---}
27 {--- result are correct if (a or b) = '' ---}
28 {--- - - - - - - - - - - - - - - - - - - ---}
29 function e_CatPath (a, b: AnsiString): AnsiString;
31 {--- remove last entry from path ---}
32 function e_UpperDir (path: AnsiString): AnsiString;
34 {--- not absolute and have no relative dirs ---}
35 function e_IsValidResourceName (name: AnsiString): Boolean;
37 {-----------------------------------------------------------------------}
38 {--- try to open/create file in one dir from `dirs` in reverse order ---}
39 {--- e_OpenResourceRW tries to create if not exists ---}
40 {--- create dirs if not exists ---}
41 {--- result <> nil, throws exceptions on errors ---}
42 {--- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ---}
43 function e_CreateResource (dirs: SSArray; name: AnsiString): TStream;
44 function e_OpenResourceRO (dirs: SSArray; name: AnsiString): TStream;
45 function e_OpenResourceRW (dirs: SSArray; name: AnsiString): TStream;
47 {--- same as shared/utils ---}
48 function e_FindResource (dirs: SSArray; var name: AnsiString; nameIsDir: Boolean = false): Boolean;
49 function e_FindWad (dirs: SSArray; name: AnsiString): AnsiString;
51 {--- returns relative wad name; never empty string ---}
52 function e_FindWadRel (dirs: SSArray; name: AnsiString): AnsiString;
54 {--- append dirs to 'path.wad:\file'. if disk is void, append defWad ---}
55 function e_GetResourcePath (dirs: SSArray; path: AnsiString; defWad: AnsiString): AnsiString;
57 {--- same as SysUtils.FinFirst ---}
58 function e_FindFirst (dirs: SSArray; name: AnsiString; attr: LongInt; out Rslt: TSearchRec): LongInt;
60 {--- try to get a writeable directory from list, throws if no one directory created ---}
61 {--- (unless `required` is `false`: in this case, returns empty string) ---}
62 {--- creates all necessary subdirs, if it can ---}
63 function e_GetWriteableDir (dirs: SSArray; required: Boolean=true): AnsiString;
65 implementation
67 uses WadReader, e_log, hashtable;
69 type
70 SpawnProc = function (pathname: AnsiString): Tstream;
72 var
73 writeableDirs: THashStrCIStr = nil;
76 function e_UpperDir (path: AnsiString): AnsiString;
77 var i: Integer;
78 begin
79 i := High(path);
80 while (i >= 1) and (path[i] <> '/') and (path[i] <> '\') do Dec(i);
81 result := Copy(path, 1, i)
82 end;
84 function HasRelativeDirs (name: AnsiString): Boolean;
85 var i: Integer; ch: Char;
86 begin
87 i := 1;
88 result := false;
89 while (result = false) and (name[i] <> #0) do
90 begin
91 ch := name[i];
92 if (ch = '/') or (ch = '\') then
93 begin
94 Inc(i);
95 if name[i] = '.' then
96 begin
97 Inc(i);
98 if name[i] = '.' then
99 begin
100 Inc(i);
101 ch := name[i];
102 result := (ch = #0) or (ch = '/') or (ch = '\')
103 end
104 end
105 end
106 else
107 begin
108 Inc(i)
109 end
110 end
111 end;
113 function HasAbsoluteDirs (name: AnsiString): Boolean;
114 begin
115 result := (name = '') or (name[1] = '/') or (name[1] = '\')
116 end;
118 function e_IsValidResourceName (name: AnsiString): Boolean;
119 begin
120 result := (HasAbsoluteDirs(name) = false) and (HasRelativeDirs(name) = false)
121 end;
123 function SpawnStream (dirs: SSArray; name: AnsiString; p: SpawnProc; createNewDir: Boolean): TStream;
124 var i: Integer;
125 begin
126 result := nil;
127 assert(dirs <> nil);
128 assert(e_IsValidResourceName(name));
129 i := High(dirs);
130 while (i >= 0) and (result = nil) do
131 begin
132 try
133 if debug_e_res then
134 e_LogWritefln(' %s', [dirs[i]]);
135 if (createNewDir = false) or (ForceDirectories(dirs[i]) = true) then
136 result := p(e_CatPath(dirs[i], name))
137 finally
138 Dec(i)
139 end
140 end
141 end;
143 function e_CreateResource (dirs: SSArray; name: AnsiString): TStream;
144 begin
145 if debug_e_res then
146 e_LogWritefln('e_CreateResource %s', [name]);
147 result := SpawnStream(dirs, name, @createDiskFile, true);
148 if result = nil then
149 raise Exception.Create('can''t create resource "' + name + '"');
150 end;
152 function e_OpenResourceRO (dirs: SSArray; name: AnsiString): TStream;
153 begin
154 if debug_e_res then
155 e_LogWritefln('e_OpenResourceRO %s', [name]);
156 result := SpawnStream(dirs, name, @openDiskFileRO, false);
157 if result = nil then
158 raise EFileNotFoundException.Create('can''t open resource "' + name + '"')
159 end;
161 function e_OpenResourceRW (dirs: SSArray; name: AnsiString): TStream;
162 begin
163 if debug_e_res then
164 e_LogWritefln('e_OpenResourceRW %s', [name]);
165 result := SpawnStream(dirs, name, @openDiskFileRW, true);
166 if result = nil then
167 raise Exception.Create('can''t create resource "' + name + '"')
168 end;
170 function e_CatPath (a, b: AnsiString): AnsiString;
171 begin
172 if a = '' then
173 result := b
174 else if b = '' then
175 result := a
176 else
177 result := a + '/' + b
178 end;
180 function e_FindResource (dirs: SSArray; var name: AnsiString; nameIsDir: Boolean = false): Boolean;
181 var i: Integer; dir: AnsiString;
182 begin
183 if debug_e_res then
184 e_LogWritefln('e_FindResource %s (%s)', [name, nameIsDir]);
185 result := false;
186 assert(dirs <> nil);
187 assert(e_IsValidResourceName(name));
188 i := High(dirs); dir := name;
189 while (i >= 0) and (result = false) do
190 begin
191 dir := e_CatPath(dirs[i], name);
192 result := findFileCI(dir, nameIsDir);
193 if debug_e_res then
194 e_LogWritefln(' %s -> %s', [dir, result]);
195 Dec(i)
196 end;
197 if result = true then
198 name := dir;
199 if debug_e_res then
200 e_LogWritefln(' result = %s (%s)', [name, result]);
201 end;
203 function e_FindWad (dirs: SSArray; name: AnsiString): AnsiString;
204 var i: Integer;
205 begin
206 if debug_e_res then
207 e_LogWritefln('e_FindWad "%s"', [name]);
208 result := '';
209 assert(dirs <> nil);
210 assert(e_IsValidResourceName(name));
211 i := High(dirs);
212 while (i >= 0) and (result = '') do
213 begin
214 result := findDiskWad(dirs[i] + DirectorySeparator + name);
215 if debug_e_res then
216 e_LogWritefln(' %s -> %s', [dirs[i] + DirectorySeparator + name, result]);
217 Dec(i)
218 end
219 end;
221 function e_FindWadRel (dirs: SSArray; name: AnsiString): AnsiString;
222 var
223 s: AnsiString;
224 maxpfx: AnsiString = '';
225 pfx: AnsiString;
226 begin
227 result := name;
228 if not findFileCI(name) then exit;
229 for s in dirs do
230 begin
231 if (length(s) = 0) then continue;
232 if (length(name) <= length(s)) then continue;
233 if (length(s) < length(maxpfx)) then continue;
234 pfx := s;
235 if not findFileCI(pfx, true) then continue;
236 if (pfx[length(pfx)] <> '/') and (pfx[length(pfx)] <> '\') then pfx := pfx+'/';
237 if (length(pfx)+1 > length(name)) then continue;
238 if (strEquCI1251(copy(name, 1, length(pfx)), pfx)) then maxpfx := pfx;
239 end;
240 if (length(maxpfx) > 0) then
241 begin
242 result := name;
243 Delete(result, 1, length(maxpfx));
244 end;
245 end;
247 function e_GetResourcePath (dirs: SSArray; path: AnsiString; defWad: AnsiString): AnsiString;
248 var diskName, fileName: AnsiString;
249 begin
250 if debug_e_res then
251 e_LogWritefln('e_GetResourcePath0 %s (%s)', [path, defWad]);
252 assert(length(dirs) > 0);
253 assert(path <> '');
254 assert(defWad <> '');
255 diskName := g_ExtractWadName(path);
256 fileName := g_ExtractFilePathName(path);
257 if diskName = '' then diskName := defWad else diskName := e_FindWad(dirs, diskName);
258 assert(diskName <> '', 'oh fuck, wad "' + diskName + '" not founded');
259 result := diskName + ':\' + fileName;
260 if debug_e_res then
261 e_LogWritefln(' this>>> %s', [result]);
262 end;
264 function e_FindFirst (dirs: SSArray; name: AnsiString; attr: LongInt; out Rslt: TSearchRec): LongInt;
265 var i: Integer; dir: AnsiString;
266 begin
267 if debug_e_res then
268 e_LogWritefln('e_FindFirst %s', [name]);
269 assert(dirs <> nil);
270 assert(e_IsValidResourceName(name));
271 i := High(dirs); result := -1;
272 while (i >= 0) and (result <> 0) do
273 begin
274 dir := dirs[i] + DirectorySeparator + name;
275 result := FindFirst(dir, attr, Rslt);
276 if debug_e_res then
277 e_LogWritefln(' %s: %s -- %s', [i, dir, result]);
278 Dec(i);
279 end
280 end;
282 // k8: sorry. i know that this sux, but checking directory access rights is unreliable (unportable).
283 function canCreateFiles (dir: AnsiString): Boolean;
284 var
285 f: Integer;
286 st: TStream = nil;
287 sr: TSearchRec;
288 fn: AnsiString;
289 begin
290 result := false;
291 for f := 0 to $7fffffff do
292 begin
293 fn := Format('%s/$$$temptest$$$_%d.$$$%d$$$', [dir, f, f]);
294 if (FindFirst(fn, faAnyFile, sr) = 0) then
295 begin
296 FindClose(sr);
297 continue;
298 end;
299 FindClose(sr);
300 try
301 st := TFileStream.Create(fn, fmCreate);
302 except // sorry
303 st := nil; // just in case
304 end;
305 if assigned(st) then
306 begin
307 st.Free();
308 try DeleteFile(fn); except end;
309 result := true;
310 end;
311 exit;
312 end;
313 end;
315 function e_GetWriteableDir (dirs: SSArray; required: Boolean=true): AnsiString;
316 var
317 f: Integer;
318 begin
319 assert(length(dirs) > 0);
320 result := '';
321 if assigned(writeableDirs) then
322 begin
323 for f := High(dirs) downto Low(dirs) do
324 begin
325 if (writeableDirs.get(dirs[f], result)) then
326 begin
327 //writeln('*** KNOWN WRITEABLE DIR: "', result, '"');
328 exit;
329 end;
330 end;
331 end;
332 for f := High(dirs) downto Low(dirs) do
333 begin
334 try
335 if ForceDirectories(dirs[f]) then
336 begin
337 result := dirs[f];
338 if (findFileCI(result, true)) then
339 begin
340 if canCreateFiles(result) then
341 begin
342 if not assigned(writeableDirs) then writeableDirs := THashStrCIStr.Create();
343 writeableDirs.put(dirs[f], result);
344 //writeln('*** NEW WRITEABLE DIR: "', result, '" ("', dirs[f], '"); rq=', required);
345 exit;
346 end;
347 end;
348 end;
349 except // sorry
350 end;
351 end;
352 if required then raise Exception.Create(Format('unable to create directory "%s"', [dirs[High(dirs)]]));
353 result := '';
354 end;
356 end.