source: tags/1.3.1/CmdList.pas

Last change on this file was 438, checked in by chronos, 2 years ago
  • Fixed: Data size was not correctly stored in server commands. Introduced in rev 435.
  • Fixed: Check data size for its maximum. Limit maximum length of unit and city name so it can fit into data block.
File size: 11.0 KB
Line 
1{$INCLUDE Switches.inc}
2unit CmdList;
3
4interface
5
6uses
7 Classes, SysUtils, Math;
8
9const
10 MaxDataSize = 1024;
11 CommandDataElementSize = 4;
12 CommandDataElementCountMask = $f;
13 CommandDataMaxSize = CommandDataElementSize * CommandDataElementCountMask;
14
15type
16 TLogData = array [0 .. 999999999] of Byte;
17
18 TCmdListState = record
19 nLog: Integer; { used size of LogData in bytes }
20 LoadPos: Integer; { position in LogData when loading a game }
21 LastMovingUnit: Integer;
22 MoveCode: Cardinal;
23 LoadMoveCode: Cardinal;
24 end;
25
26 TCmdList = class
27 constructor Create;
28 destructor Destroy; override;
29 procedure Get(var Command, Player, Subject: integer; var Data: pointer);
30 procedure GetDataChanges(Data: pointer; DataSize: integer);
31 procedure Put(Command, Player, Subject: integer; Data: pointer);
32 procedure PutDataChanges(Command, Player: integer;
33 OldData, NewData: pointer; DataSize: integer);
34 procedure LoadFromFile(const f: TFileStream);
35 procedure SaveToFile(const f: TFileStream);
36 procedure AppendToFile(const f: TFileStream; const OldState: TCmdListState);
37 procedure Cut;
38 function Progress: integer;
39 private
40 LogAlloc: integer; { allocated size of LogData in bytes }
41 LogData: ^TLogData;
42 FState: TCmdListState;
43 procedure PutData(Data: pointer; Length: integer);
44 procedure CompleteMoveCode;
45 public
46 property State: TCmdListState read FState write FState;
47 end;
48
49 function CommandWithData(Command: Integer; DataSize: Byte): Integer;
50
51resourcestring
52 SCommandDataSizeError = 'Command data size %d out of range (0-%d).';
53
54
55implementation
56
57uses
58 Protocol;
59
60const
61 LogGrow = 1 shl 18;
62
63type
64 TData = array [0 .. MaxDataSize - 1] of Cardinal;
65 PData = ^TData;
66
67function CommandWithData(Command: Integer; DataSize: Byte): Integer;
68var
69 DataElementCount: Byte;
70begin
71 if DataSize > CommandDataMaxSize then
72 raise Exception.Create(Format(SCommandDataSizeError, [DataSize, CommandDataMaxSize]));
73 DataElementCount := Ceil(DataSize / CommandDataElementSize);
74 Result := Command or (DataElementCount and CommandDataElementCountMask);
75end;
76
77constructor TCmdList.Create;
78begin
79 inherited;
80 FState.nLog := 0;
81 LogAlloc := 0;
82 LogData := nil;
83 FState.LastMovingUnit := -1;
84 FState.MoveCode := 0;
85 FState.LoadMoveCode := 0;
86end;
87
88destructor TCmdList.Destroy;
89begin
90 ReallocMem(LogData, 0);
91 inherited;
92end;
93
94procedure TCmdList.Get(var Command, Player, Subject: integer; var Data: pointer);
95var
96 DirCode: Cardinal;
97 Code: Cardinal;
98begin
99 if FState.LoadMoveCode > 0 then
100 begin
101 Player := -1;
102 if FState.LoadMoveCode and 1 = 1 then
103 begin // FM
104 DirCode := FState.LoadMoveCode shr 1 and 7;
105 Subject := FState.LastMovingUnit;
106 FState.LoadMoveCode := FState.LoadMoveCode shr 4;
107 end
108 else
109 begin // M
110 DirCode := FState.LoadMoveCode shr 3 and 7;
111 Subject := FState.LoadMoveCode shr 6 and $FFF;
112 FState.LoadMoveCode := FState.LoadMoveCode shr 18;
113 FState.LastMovingUnit := Subject
114 end;
115 case DirCode of
116 0: Command := sMoveUnit + $090;
117 1: Command := sMoveUnit + $0F0;
118 2: Command := sMoveUnit + $390;
119 3: Command := sMoveUnit + $3F0;
120 4: Command := sMoveUnit + $020;
121 5: Command := sMoveUnit + $060;
122 6: Command := sMoveUnit + $100;
123 7: Command := sMoveUnit + $300;
124 end;
125 Data := nil;
126 end
127 else
128 begin
129 code := Cardinal((@LogData[FState.LoadPos])^);
130 if code and 3 = 0 then
131 begin // non-clientex command
132 Command := code shr 2 and $3FFF + sExecute;
133 Player := code shr 16 and $F;
134 Subject := code shr 20 and $FFF;
135 inc(FState.LoadPos, 4);
136 end
137 else if code and 7 = 2 then
138 begin // clientex command
139 Command := code shr 3 and $FFFF;
140 Player := code shr 19 and $F;
141 Subject := 0;
142 inc(FState.LoadPos, 3);
143 end
144 else
145 begin // move command shortcut
146 if (code and 1 = 1) and (code and (7 shl 4) <> 6 shl 4) then
147 begin
148 FState.LoadMoveCode := code and $FF;
149 inc(FState.LoadPos);
150 end
151 else
152 begin
153 FState.LoadMoveCode := code and $FFFFFF;
154 inc(FState.LoadPos, 3);
155 end;
156 Get(Command, Player, Subject, Data);
157 Exit;
158 end;
159
160 if Command and CommandDataElementCountMask = 0 then
161 Data := nil
162 else
163 begin
164 Data := @LogData[FState.LoadPos];
165 inc(FState.LoadPos, Command and CommandDataElementCountMask * CommandDataElementSize);
166 end;
167 end;
168end;
169
170procedure TCmdList.GetDataChanges(Data: pointer; DataSize: integer);
171var
172 b0, b1: integer;
173 Map0, Map1: Cardinal;
174begin
175 Map0 := Cardinal((@LogData[FState.LoadPos])^);
176 inc(FState.LoadPos, 4);
177 b0 := 0;
178 while Map0 > 0 do begin
179 if Map0 and 1 <> 0 then begin
180 Map1 := Cardinal((@LogData[FState.LoadPos])^);
181 inc(FState.LoadPos, 4);
182 for b1 := 0 to 31 do
183 if 1 shl b1 and Map1 <> 0 then begin
184 if b0 * 32 + b1 < DataSize then
185 PData(Data)[b0 * 32 + b1] := Cardinal((@LogData[FState.LoadPos])^);
186 inc(FState.LoadPos, 4);
187 end;
188 end;
189 inc(b0);
190 Map0 := Map0 shr 1;
191 end;
192end;
193
194procedure TCmdList.Put(Command, Player, Subject: integer; Data: pointer);
195var
196 DirCode, code: Cardinal;
197begin
198 if Command and $FC00 = sMoveUnit then
199 begin // move command shortcut
200 case Command of
201 sMoveUnit + $090: DirCode := 0;
202 sMoveUnit + $0F0: DirCode := 1;
203 sMoveUnit + $390: DirCode := 2;
204 sMoveUnit + $3F0: DirCode := 3;
205 sMoveUnit + $020: DirCode := 4;
206 sMoveUnit + $060: DirCode := 5;
207 sMoveUnit + $100: DirCode := 6;
208 sMoveUnit + $300: DirCode := 7;
209 end;
210 if Subject = FState.LastMovingUnit then
211 code := 1 + DirCode shl 1
212 else
213 code := 6 + DirCode shl 3 + Cardinal(Subject) shl 6;
214 if FState.MoveCode = 0 then
215 FState.MoveCode := code
216 else if FState.MoveCode and 1 = 1 then
217 begin // FM + this
218 FState.MoveCode := FState.MoveCode + code shl 4;
219 if code and 1 = 1 then
220 PutData(@FState.MoveCode, 1) // FM + FM
221 else
222 PutData(@FState.MoveCode, 3); // FM + M
223 FState.MoveCode := 0;
224 end
225 else if code and 1 = 1 then
226 begin // M + FM
227 FState.MoveCode := FState.MoveCode + code shl 18;
228 PutData(@FState.MoveCode, 3);
229 FState.MoveCode := 0;
230 end
231 else // M + M
232 begin
233 PutData(@FState.MoveCode, 3);
234 FState.MoveCode := code;
235 end;
236 FState.LastMovingUnit := Subject;
237 end
238 else
239 begin
240 CompleteMoveCode;
241 if Command >= cClientEx then
242 begin
243 code := 2 + Command shl 3 + Player shl 19;
244 PutData(@code, 3);
245 end
246 else
247 begin
248 code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16 +
249 Cardinal(Subject) shl 20;
250 PutData(@code, 4);
251 end;
252 end;
253 if Command and CommandDataElementCountMask <> 0 then
254 PutData(Data, Command and CommandDataElementCountMask * CommandDataElementSize);
255end;
256
257procedure TCmdList.PutDataChanges(Command, Player: integer;
258 OldData, NewData: pointer; DataSize: integer);
259var
260 MapPos, LogPos, b0, b1, RowEnd: integer;
261 Map0, Map1, code: Cardinal;
262begin
263 if DataSize <= 0 then
264 exit;
265 if DataSize > MaxDataSize then
266 DataSize := MaxDataSize;
267 CompleteMoveCode;
268 MapPos := FState.nLog + 8;
269 LogPos := MapPos + 4;
270 Map0 := 0;
271 for b0 := 0 to (DataSize - 1) div 32 do
272 begin
273 if LogPos + 4 * 32 > LogAlloc then
274 begin
275 inc(LogAlloc, LogGrow);
276 ReallocMem(LogData, LogAlloc);
277 end;
278 Map0 := Map0 shr 1;
279 Map1 := 0;
280 RowEnd := DataSize - 1;
281 if RowEnd > b0 * 32 + 31 then
282 RowEnd := b0 * 32 + 31;
283 for b1 := b0 * 32 to RowEnd do
284 begin
285 Map1 := Map1 shr 1;
286 if PData(NewData)[b1] <> PData(OldData)[b1] then
287 begin
288 Cardinal((@LogData[LogPos])^) := PData(NewData)[b1];
289 inc(LogPos, 4);
290 inc(Map1, $80000000);
291 end;
292 end;
293 if Map1 > 0 then
294 begin
295 Map1 := Map1 shr (b0 * 32 + 31 - RowEnd);
296 Cardinal((@LogData[MapPos])^) := Map1;
297 MapPos := LogPos;
298 inc(LogPos, 4);
299 inc(Map0, $80000000);
300 end;
301 end;
302 if Map0 = 0 then
303 exit; // no changes
304
305 Map0 := Map0 shr (31 - (DataSize - 1) div 32);
306 Cardinal((@LogData[FState.nLog + 4])^) := Map0;
307 code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16;
308 Cardinal((@LogData[FState.nLog])^) := code;
309 FState.nLog := MapPos;
310end;
311
312procedure TCmdList.PutData(Data: pointer; Length: integer);
313begin
314 if FState.nLog + Length > LogAlloc then
315 begin
316 inc(LogAlloc, LogGrow);
317 ReallocMem(LogData, LogAlloc);
318 end;
319 move(Data^, LogData[FState.nLog], Length);
320 inc(FState.nLog, Length);
321end;
322
323procedure TCmdList.CompleteMoveCode;
324begin
325 if FState.MoveCode > 0 then
326 begin
327 if FState.MoveCode and 1 = 1 then
328 PutData(@FState.MoveCode, 1) // Single FM
329 else
330 PutData(@FState.MoveCode, 3); // Single M
331 FState.MoveCode := 0;
332 end;
333end;
334
335procedure TCmdList.LoadFromFile(const f: TFileStream);
336begin
337 f.read(FState.nLog, 4);
338 LogData := nil;
339 LogAlloc := ((FState.nLog + 2) div LogGrow + 1) * LogGrow;
340 ReallocMem(LogData, LogAlloc);
341 f.read(LogData^, FState.nLog);
342 FState.LoadPos := 0;
343end;
344
345procedure TCmdList.SaveToFile(const f: TFileStream);
346begin
347 CompleteMoveCode;
348 f.write(FState.nLog, 4);
349 f.write(LogData^, FState.nLog);
350end;
351
352procedure TCmdList.AppendToFile(const f: TFileStream;
353 const OldState: TCmdListState);
354begin
355 CompleteMoveCode;
356 f.write(FState.nLog, 4);
357 f.Position := f.Position + OldState.nLog;
358 f.write(LogData[OldState.nLog], FState.nLog - OldState.nLog);
359end;
360
361procedure TCmdList.Cut;
362begin
363 FState.nLog := FState.LoadPos;
364end;
365
366function TCmdList.Progress: integer;
367begin
368 if (FState.LoadPos = FState.nLog) and (FState.LoadMoveCode = 0) then
369 result := 1000 // loading complete
370 else if FState.nLog > 1 shl 20 then
371 result := (FState.LoadPos shr 8) * 999 div (FState.nLog shr 8)
372 else
373 result := FState.LoadPos * 999 div FState.nLog;
374end;
375
376{ Format Specification:
377
378 Non-ClientEx-Command:
379 Byte3 Byte2 Byte1 Byte0
380 ssssssss sssspppp cccccccc cccccc00
381 (c = Command-sExecute, p = Player, s = Subject)
382
383 ClientEx-Command:
384 Byte2 Byte1 Byte0
385 0ppppccc cccccccc ccccc010
386 (c = Command, p = Player)
387
388 Single Move:
389 Byte2 Byte1 Byte0
390 000000ss ssssssss ssaaa110
391 (a = Direction, s = Subject)
392
393 Move + Follow Move:
394 Byte2 Byte1 Byte0
395 00bbb1ss ssssssss ssaaa110
396 (a = Direction 1, s = Subject 1, b = Direction 2)
397
398 Follow Move + Move:
399 Byte2 Byte1 Byte0
400 00ssssss ssssssbb b110aaa1
401 (a = Direction 1, b = Direction 2, s = Subject 2)
402
403 Single Follow Move:
404 Byte0
405 0000aaa1
406 (a = Direction)
407
408 Double Follow Move:
409 Byte0
410 bbb1aaa1
411 (a = Direction 1, b = Direction 2)
412}
413
414end.
Note: See TracBrowser for help on using the repository browser.