USSRxAZ Опубликовано 12 апреля, 2011 Жалоба Поделиться Опубликовано 12 апреля, 2011 (изменено) Итак, переношу сюда дискуссию связанную с особенностями записи игр в файлы повтора .rec Основная информация представлена в этой теме на официальном форуме игры "формат файла 2005" DATASDSC // START OF NEW CHUNK 2 // Chunk version (?) 95 // Chunk data size 0 // A variable string length 2 // (?) 2 // Number of teams (?) 6 // Number of players (?) 513 // Map Size 4 // String length W40k // Game/engine name 7 // Double-byte string length $.1.0.0.1.5.0. // Double-byte engine version string (?) 33 // A variable string length DATA:Scenarios\MP\6P_KASYR_LUTIEN // Map name 4269834555 // Could this be the duration/time (?) 0 // (?) 0 // (?) 0 // (?) DATABASE // START OF NEW CHUNK 4 // Chunk version (?) 125 // Chunk data size 0 // A variable string length 3 // (?) 8 // (?) 3853256620 // (?) 8 // (?) 1 // AIDF value FDIA //= AIDF (AI Difficulty) - It took me a while to figure these out...backwards-endian :) 0 // RSST value TSSR //= RSST (Starting Resources) 0 // LKTM value MTKL //= LKTM (Lock Teams) 1 // CHEA value AEHC //= CHEA (Cheats Enabled) 0 // SLOC value COLS //= SLOC (Starting Location) 2 // GSPD value DPSG //= GSPD (Game Speed) 0 // RSSH value HSSR //= RSSH (Resource Sharing) 1 // RSRT value TRSR //= RSRT (Resource Rate) 0 // NOTE THIS IS A SINGLE ZERO BYTE (NOT A DWORD) 14 // Double-byte string length 3.v.3. .C.a.b.l.e. .O.n.l.y. // Double-byte game name 0 // (?) 1 // This is the number of Win conditions that are set. Each one is a constant DWORD that follows. 4137259138 // Win Condition - Annihilate FOLDGPLY // START OF NEW CHUNK - GPLY = Game Player? There is one of these chunks for each player. 2 // Chunk version (?) 41330 // Chunk data size 0 // A variable string length DATAINFO // START OF NEW CHUNK 1 // Chunk version (?) 54 // Chunk data size 0 // A variable string length 12 // Double-byte string length B.e.e.z.e.r._.S.m.u.r.f. // Double-byte player name 2 // (?) 0 // (?) 10 // A string length eldar_race // Race name 0 // (?) FOLDTCUC // START OF NEW CHUNK - TCUC = Team Colors, something, something? 1 // Chunk version (?) 84 // Chunk data size 0 // A variable string length DATALCIN // START OF NEW CHUNK - LCIN = Local?? NOTE: This section differs for the replay's "local" player 1 // Chunk version (?) 12 // Chunk data size 0 // A variable string length 0 // (?) 0 // (?) 0 // (?) DATAUNCU // START OF NEW CHUNK - UNCU = ???? 1 // Chunk version (?) 32 // Chunk data size 0 // A variable string length 4 // Double-byte string length T.e.s.t. // Double-byte scheme name FF FF FF FF // Primary NOTE: Colors are BGRA (blue, green, red, alpha) FF FF FF FF // Secondary FF FF FF FF // Trim FF FF FF FF // Weapons FF FF FF FF // Eyes FOLDTCBD // START OF NEW CHUNK - TCBD = Team color Badge? 1 // Chunk version (?) 16460 // Chunk data size - A bigish chunk...hence the transferring badges lag :) 0 // A variable string length FOLDIMAG // START OF NEW CHUNK - IMAG = Image (badge in this case) 1 // Chunk version (?) 16440 // Chunk data size 0 // A variable string length DATAATTR // START OF NEW CHUNK - ATTR = Image attribute(s)? 2 // Chunk version (?) 16 // Chunk data size 0 // A variable string length 0 // (?) 64 // Image WIDTH 64 // Image HEIGHT 1 // (?) DATADATA // START OF NEW CHUNK - DATA = Image data? 2 // Chunk version (?) 1024 // Chunk data size 0 // A variable string length <image data> // The image color values FOLDTCBN // START OF NEW CHUNK - TCBN = Team color Banner? 1 // Chunk version (?) 24652 // Chunk data size 0 // A variable string length FOLDIMAG // START OF NEW CHUNK - IMAG = Image (banner in this case) 1 // Chunk version (?) 1152 // Chunk data size 0 // A variable string length DATAATTR // START OF NEW CHUNK - ATTR = Image attribute(s)? 2 // Chunk version (?) 16 // Chunk data size 0 // A variable string length 0 // (?) 64 // Image WIDTH 96 // Image HEIGHT 1 // (?) DATADATA // START OF NEW CHUNK - DATA = Image data? 2 // Chunk version (?) 1536 // Chunk data size <image data> // The image color values FOLDGPLY... "формат файла 2006" @Pos: 0 [DWORD] // Replay Version 1 < Patch 1.3 with Patch 1.3 it got 2 after Patch 1.4 it goes to 4 [TEXT]->20Byte File Type "W40K_RECRelic Chunky" [DWORD] // ??? [DWORD] // ??? [DWORD] // ??? [KEYWORD](FOLDPOST) // START OF NEW CHUNK [DWORD] // Chunk version [DWORD] // ??? [DWORD] // ??? [DWORD] // ??? [bYTE] // ??? [KEYWORD](DATADATA) // START OF NEW CHUNK [DWORD] // Chunk version [DWORD] // ??? [DWORD] // ??? [DWORD] // Total game duration [TEXT]->12Byte // Relic Chunky [DWORD] // Chunk version [DWORD] // ??? [DWORD] // ??? [KEYWORD](FOLDINFO) // START OF NEW CHUNK [DWORD] // Chunk version [DWORD] // 4 byte info ? [DWORD] // ??? [TEXT]->8Byte // GameInfo [bYTE] // ??? [KEYWORD](FOLDWAN) // START OF NEW CHUNK [DWORD] // Chunk version [DWORD] // ??? [DWORD] // ??? [KEYWORD](DATASDSC) // START OF NEW CHUNK [DWORD] // Chunk version (?) always 1 so far 2 for Version 4 [DWORD] // Chunk data size [DWORD] // A variable string length [DWORD] // (?) [DWORD] // Number of teams (?) [DWORD] // Number of active players (without spectators) [DWORD] // Map Size [DWORD] // String length [DWORD] // Game/engine name [DWORD]->%length% // length of additional header infos [uNICODETEXT] // Double-byte engine version string (%length%*2) [DWORD]->%length% // lenth of map Name [TEXT] // Map name (%length%) [DWORD] // (?) [DWORD] // (?) [DWORD] // (?) [DWORD] // (?) {REP VERSION 1} [DWORD] // (?) [bYTE] // (?) [KEYWORD](FOLDMODI) // START OF NEW CHUNK [DWORD] // Chunk version (?) always 1 so far [DWORD] // (?) [DWORD] // (?) [KEYWORD](DATADMOD) // START OF NEW CHUNK [DWORD] // Chunk version (?) always 4 so far [DWORD]->%length% // Chunk data size [TEXT] // (?) (%length%) [KEYWORD](DATABASE) // START OF NEW CHUNK [DWORD] // Chunk version (?) always 4 so far [DWORD] // Chunk data size {END VERSION 1} {REP VERSION 2 OR 4} [KEYWORD](DATABASE) // START OF NEW CHUNK [DWORD] // Chunk version (?) always 4 so far [DWORD] // Chunk data size {END VERSION 2} [DWORD] // Always 0 so far [DWORD] // Always 3 so far [DWORD] // Avavible Slots (after Patch 1.3 always 8 cause spec slots) [DWORD] // (?) [DWORD] // Always 8 so far [DWORD] // (?) {8 Keytext with 1byte value - not sorted!} [DWORD] FDIA //= AIDF (AI Difficulty) - It took me a while to figure these out...backwards-endian [DWORD] // 1. Byte is the value [DWORD] TSSR //= RSST (Starting Resources) [DWORD] // 1. Byte is the value [DWORD] MTKL //= LKTM (Lock Teams) [DWORD] // 1. Byte is the value [DWORD] AEHC //= CHEA (Cheats Enabled) [DWORD] // 1. Byte is the value [DWORD] COLS //= SLOC (Starting Location) [DWORD] // 1. Byte is the value [DWORD] DPSG //= GSPD (Game Speed) [DWORD] // 1. Byte is the value [DWORD] HSSR //= RSSH (Resource Sharing) [DWORD] // 1. Byte is the value [DWORD] TRSR //= RSRT (Resource Rate) [bYTE] // The Byte is the value [DWORD]->%length% // length of ingame Name [uNICODETEXT] // Double-byte game name (%length% * 2) [DWORD] // End of Gamename Always 0 so far [DWORD]->%length% // This is the number of Win conditions that are set. Each one is a constant DWORD that follows. {%length% win conditions} [DWORD] // Win conditon -157708158 Annihilate -1826760460 SuddenDeath -1158102879 Assassinate -779857721 EconomicVictory 735076042 ControlArea 863969525 DestroyHQ 1959084950 TakeandHolde {END Win Conditions} {Now for any Slot There come Player data} [KEYWORD](FOLDGPLY) // START OF NEW CHUNK - GPLY = Game Player? There is one of these chunks for each player. [DWORD] 2 // Chunk version (?) Always 2 so far [DWORD] 41330 // Chunk data size [DWORD] 0 // A variable string length Always 0 so far [KEYWORD](DATAINFO) // START OF NEW CHUNK [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 54 // Chunk data size [DWORD] 0 // A variable string length Always 0 so far [DWORD]->%length% // length of Plyer Name [uNICODETEXT] // Double-byte player name (%length% * 2) [DWORD] // Type of Slot (0 Host/2 player/4 specc/7 empty/11 computer) [DWORD] // Team of the Player (counter starts with 0) [DWORD]->%length% // lengt of race string [TEXT] // Race Name (%length%) [DWORD] // (?) Always 0 so far {REP VERSION 4} //[DWORD]->%length% // (?) Always 0 so far //[TEXT] // ? (%length%) //After Version 1.5 there was this new Flag. //[bYTE] ??? //It is only present in Version > 1.5 {END VERSION 4} [KEYWORD](FOLDTCUC) // START OF NEW CHUNK - TCUC = Team Colors, something, something? {Also Possible: FOLDGPLY -> New Player beginns (happens with spec slots)} [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 84 // Chunk data size [DWORD] 0 // A variable string length Always 0 so far [KEYWORD](DATALCIN) // START OF NEW CHUNK - LCIN = Local?? NOTE: This section differs for the replay's "local" player [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD]->%length% 12 // Chunk data size [TEXT] // (?) (%length%) [DWORD] // (?) Always 0 so far [KEYWORD](DATAUNCU) // START OF NEW CHUNK - UNCU = ???? [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 32 // Chunk data size [DWORD] 0 // A variable string length Always 0 so far [DWORD]->%length% // length of ColorScheme [uNICODETEXT] // Double-byte scheme name (%length% * 2) [DWORD] // Primary NOTE: Colors are BGRA (blue, green, red, alpha) [DWORD] // Secondary [DWORD] // Trim [DWORD] // Weapons [DWORD] // Eyes [KEYWORD](FOLDTCBD) // START OF NEW CHUNK - TCBD = Team color Badge? {Also Possible: FOLDGPLY -> New Player beginns (happens sometimes...)} {Another keyword FOLDTCBD -> Badge/Banner sometimes change their position in the Replay} {If none of this Keywords is matchetd it seams like an broken header.. So ignore the 8byte keyword and go on with action data} [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 16460 // Chunk data size - A bigish chunk...hence the transferring badges lag [DWORD] 0 // A variable string length [KEYWORD](FOLDIMAG) // START OF NEW CHUNK - IMAG = Image (badge in this case) [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 16460 // Chunk data size [DWORD]->%length% // A variable string length {IF %length% GT 0 then the Badge has a special Name} [TEXT] // Name of the Badge (%length%) {END} [KEYWORD](DATAATTR) // START OF NEW CHUNK - ATTR = Image attribute(s)? [DWORD] 2 // Chunk version (?) Always 2 so far [DWORD] 16 // Chunk data size [DWORD] 0 // (?) [DWORD] 0 // (?) [DWORD] 64 // Image WIDTH [DWORD] 64 ´ // Image HEIGHT [DWORD] 1 // (?) Always 1 so far [KEYWORD](DATADATA) // START OF NEW CHUNK - DATA = Image data? [DWORD] 2 // Chunk version (?) Always 2 so far [DWORD] 16 // Chunk data size [DWORD] 0 // (?) {Now comes the image data. Pixel by pixel form heigth to width in RGBA colors - you also have to mirror the picture} [KEYWORD](FOLDTCBN) // START OF NEW CHUNK - TCBN = Team color Banner? [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 24652 // Chunk data size [DWORD] 0 // (?) [KEYWORD](FOLDIMAG ) // START OF NEW CHUNK - IMAG = Image (banner in this case) [DWORD] 1 // Chunk version (?) Always 1 so far [DWORD] 1152 // Chunk data size [DWORD] 0 // (?) [KEYWORD](DATAATTR) // START OF NEW CHUNK - ATTR = Image attribute(s)? [DWORD] 2 // Chunk version (?) Always 2 so far [DWORD] 16 // Chunk data size [DWORD] 0 // (?) [DWORD] 0 // (?) [DWORD] 64 // Image WIDTH [DWORD] 96 ´ // Image HEIGHT [DWORD] 1 // (?) Always 1 so far [KEYWORD](DATADATA) // START OF NEW CHUNK - DATA = Image data? [DWORD] 2 // Chunk version (?) Always 2 so far [DWORD] 1536 // Chunk data size [DWORD] 0 // (?) {Now comes the image data. Pixel by pixel form heigth to width in RGBA colors - you also have to turn the picture} {End of Player Data} {Now The acion Data starts.. DoW is working with 8Ticks per second} [DWORD] // Action 1 Key - Action Key 1 identefies an Chat msg so far [DWORD] // Action 1 Length {IF second action key is 1} [DWORD] // Action 2 Key {IF this action key is also 1 its an chat msg} [DWORD] // Action 2 Length of Chat chunk [bYTE] // Also length of Chat chunk so far {IF REP Version 1} [bYTE] // Player ID (As same orders as they are listed bevore) [bYTE] // Reciver 0 All/1 Team/2 System [bYTE] // (?) {IF Rep Version 2 OR 4} [DWORD]->%len% // Length of Player Name [uNICODETEXT] // Double-byte player name (%len% * 2) [DWORD] // Player ID > 1000 are normal players blow the IDs of the Spectators [DWORD] // Msg Type 0 Player/1 Spec/2 System [DWORD] // Reciver 0 All/1 Team/2 System {End of Version spec Data} [DWORD]->%len% // Length of Chat MSG [uNICODETEXT] // Double-byte chat msg (%len% * 2) {If second Action Key is not 1} [DWORD]->%length% // length of action [bYTE] // (?) [TEXT] // (?) (%length%) {If action key is 0} [bYTE] {IF REP version 2} [bYTE] // Indicatios if the action block has action (1) or not (0) {END Version depanding handling} [DWORD] // Number of this tick (8 per second!) "исходник менеджера реплеев на C#" /* * Created by SharpDevelop. * User: Norbert 'GoWa' Laenger * Date: 23.02.2006 * Time: 13:04 */ // DoW/DoW:WA Replay Reader. Analyse and Show informatios from replay files. // Copyright © Norbert 'GoWa' Laenger name of author // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. using System; using System.IO; using System.Collections; using System.Text; using System.Drawing; using System.Windows.Forms; using System.Threading; namespace DoWRepReader { /// <summary> /// Description of Class1. /// </summary> // Methods public class RepReader { #region Variable deklaration public Replay Replay; private UnicodeEncoding encoding1; private int counter; private byte[] buffer; private int num; private char[] chr; private string str; private byte wincondvalue; private int LastTick; private ChatMessage Chat; private Player Player; private Analyse Cache; private BinaryReader BinReader; private BinaryWriter BinWriter; #endregion #region Events // Events public event EventHandler ReplayChanged; #endregion #region RepReader public RepReader() { this.Chat = new ChatMessage(); this.Player = new Player(); this.encoding1 = new UnicodeEncoding(); } #endregion #region TickToTime public static string TickToTime(int Tick) { Tick = Tick / 8; int sec = Tick % 60; int min = (Tick - sec) / 60; return (min.ToString("D2") + ":" + sec.ToString("D2")); } #endregion #region RenameIngame public bool RenameIngame(string FileName, string name) { if(this.Replay.NameStart > 0) { if(this.OpenFile(FileName)) { if(this.WriteFile(FileName + "rename.rec")) { this.BinReader.BaseStream.Position = 0; this.BinWriter.BaseStream.Position = 0; while(this.BinWriter.BaseStream.Position < this.Replay.BeginFOLDINFO) { this.BinWriter.Write(this.BinReader.ReadByte()); } this.BinReader.BaseStream.Position = this.BinReader.BaseStream.Position + 4; int len = this.Replay.LengthFOLDINFO - (this.Replay.Name.Length * 2) + (name.Length * 2); this.BinWriter.Write(len); while(this.BinWriter.BaseStream.Position < this.Replay.BeginDATABASE) { this.BinWriter.Write(this.BinReader.ReadByte()); } //this.BinWriter.BaseStream.Position = this.Replay.BeginDATABASE; this.BinReader.BaseStream.Position = this.BinReader.BaseStream.Position + 4; len = this.Replay.LengthDATABASE - (this.Replay.Name.Length * 2) + (name.Length * 2); this.BinWriter.Write(len); while(this.BinWriter.BaseStream.Position < this.Replay.NameStart) { this.BinWriter.Write(this.BinReader.ReadByte()); } //this.BinWriter.BaseStream.Position = this.Replay.NameStart; this.BinReader.BaseStream.Position = this.BinReader.BaseStream.Position + 4; len = name.Length; this.BinWriter.Write(len); for(int i=0; i < name.Length; i++) { Int16 ka = (Int16) name; this.BinWriter.Write(ka); } this.BinReader.BaseStream.Position = this.BinReader.BaseStream.Position + (this.Replay.Name.Length * 2); while (BinReader.BaseStream.Position < BinReader.BaseStream.Length) { this.BinWriter.Write(this.BinReader.ReadByte()); } this.BinReader.Close(); this.BinWriter.Close(); File.Delete(FileName); File.Move(FileName + "rename.rec", FileName); return true; } else { this.BinReader.Close(); this.BinWriter.Close(); return false; } } else { this.BinReader.Close(); this.BinWriter.Close(); return false; } } else { this.BinReader.Close(); this.BinWriter.Close(); return false; } } #endregion #region OpenFile private bool OpenFile(string FileName) { try { Stream ReplayFile = File.OpenRead(FileName); this.BinReader = new BinaryReader(ReplayFile, Encoding.ASCII); return true; } catch (Exception) { this.BinReader.Close(); return false; } } #endregion #region WriteFile private bool WriteFile(string FileName) { try { Stream ReplayFile = File.OpenWrite(FileName); this.BinWriter = new BinaryWriter(ReplayFile, Encoding.ASCII); return true; } catch (Exception) { this.BinWriter.Close(); return false; } } #endregion #region ReadHeaderOnly public bool ReadHeaderOnly(string FileName) { bool ret = this.ReadHeader(FileName); BinReader.Close(); return ret; } #endregion #region ReadHeader public bool ReadHeader(string FileName) { this.Replay = new Replay(); //MessageBox.Show("Header Start..."); if(this.OpenFile(FileName)) { this.Replay.FileName = FileName; //@Pos: 0 //[DWORD] // Replay Version 1 < Patch 1.3 with Patch 1.3 it got 2 after Patch 1.4 it goes to 4 this.Replay.Version = BinReader.ReadInt32(); //[TEXT]->20Byte File Type "W40K_RECRelic Chunky" BinReader.ReadChars(20); //[DWORD] // ??? BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[KEYWORD](FOLDPOST) // START OF NEW CHUNK BinReader.ReadChars(8); // FOLDPOST //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // ??? BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadChars(12); // PostGameInfo //[bYTE] // ??? BinReader.ReadByte(); //[KEYWORD](DATADATA) // START OF NEW CHUNK BinReader.ReadChars(8); // DATADATA //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // ??? BinReader.ReadInt32(); // 4 //[DWORD] // ??? BinReader.ReadInt32(); // 0 //[DWORD] // Total game duration int TotalTicks = BinReader.ReadInt32(); this.Replay.Duration = TickToTime(TotalTicks); //[TEXT]->12Byte BinReader.ReadChars(12); // Relic Chunky //[DWORD] // ??? BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[KEYWORD](FOLDINFO) // START OF NEW CHUNK BinReader.ReadChars(8); // FOLDINFO //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // 4 byte info ? this.Replay.BeginFOLDINFO = this.BinReader.BaseStream.Position; this.Replay.LengthFOLDINFO = BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadInt32(); // 9 //[TEXT]->8Byte BinReader.ReadChars(8); // GameInfo //[bYTE] // ??? BinReader.ReadByte(); //[KEYWORD](FOLDWAN) // START OF NEW CHUNK BinReader.ReadChars(8); // FOLDWMAN //[DWORD] // ??? BinReader.ReadInt32(); // 1 //[DWORD] // ??? BinReader.ReadInt32(); //[DWORD] // ??? BinReader.ReadInt32(); //[KEYWORD](DATASDSC) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] // Chunk version (?) always 1 so far 2 for Version 4 BinReader.ReadInt32(); //[DWORD] // Chunk data size BinReader.ReadInt32(); //[DWORD] // A variable string length BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); //[DWORD] // Number of teams (?) BinReader.ReadInt32(); //[DWORD] // Number of active players (without spectators) this.Replay.PlayerCount = BinReader.ReadInt32(); //[DWORD] // Map Size this.Replay.Size = BinReader.ReadInt32(); //[DWORD] // String length num = BinReader.ReadInt32(); if(num > 0) { //[TEXT] // Game/engine name chr = BinReader.ReadChars(num); this.Replay.MOD = str = new string(chr); } else { //[DWORD] // Game/engine name BinReader.ReadInt32(); } //[DWORD]->%length% // length of additional header infos num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte engine version string (%length%*2) buffer = BinReader.ReadBytes(num * 2); this.Replay.Engine = encoding1.GetString(buffer); //[DWORD]->%length% // lenth of map Name num = BinReader.ReadInt32(); //[TEXT] // Map name (%length%) chr = BinReader.ReadChars(num); this.Replay.Map = str = new string(chr); //[DWORD] // (?) BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); // //{REP VERSION 1} if(this.Replay.Version == 1) { //[DWORD] // (?) BinReader.ReadInt32(); //[bYTE] // (?) BinReader.ReadByte(); //[KEYWORD](FOLDMODI) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] // Chunk version (?) always 1 so far BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); //[DWORD] // (?) BinReader.ReadInt32(); //[KEYWORD](DATADMOD) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] // Chunk version (?) always 4 so far BinReader.ReadInt32(); //[DWORD]->%length% // Chunk data size num = BinReader.ReadInt32(); //[TEXT] // (?) (%length%) chr = BinReader.ReadChars(num); str = new string(chr); //[TEXT] // (?) BinReader.ReadInt32(); //[KEYWORD](DATABASE) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] // Chunk version (?) always 4 so far BinReader.ReadInt32(); //{END VERSION 1} // } else if(this.Replay.Version == 2 || this.Replay.Version == 4) { //{REP VERSION 2} //[KEYWORD](DATABASE) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] // Chunk version (?) always 4 so far BinReader.ReadInt32(); //{END VERSION 2} // } //[DWORD] // Chunk data size this.Replay.BeginDATABASE = BinReader.BaseStream.Position; this.Replay.LengthDATABASE = BinReader.ReadInt32(); //[DWORD] // Always 0 so far BinReader.ReadInt32(); //[DWORD] // Always 3 so far BinReader.ReadInt32(); //[DWORD] // Avavible Slots (after Patch 1.3 always 8 cause spec slots) this.Replay.Slots = BinReader.ReadInt32(); //[DWORD] // (?) int gr = BinReader.ReadInt32(); //[DWORD] // Always 8 so far BinReader.ReadInt32(); //[DWORD] // (?) int um = BinReader.ReadInt32(); //MessageBox.Show(this.Replay.Slots.ToString() + ": " + um.ToString()); // for(counter=0;counter < 8;counter++) { //{8 Keytext with 1byte value - not sorted!} //[DWORD] FDIA //= AIDF (AI Difficulty) - It took me a while to figure these out...backwards-endian //[DWORD] // 1. Byte is the value //[DWORD] TSSR //= RSST (Starting Resources) //[DWORD] // 1. Byte is the value //[DWORD] MTKL //= LKTM (Lock Teams) //[DWORD] // 1. Byte is the value //[DWORD] AEHC //= CHEA (Cheats Enabled) //[DWORD] // 1. Byte is the value //[DWORD] COLS //= SLOC (Starting Location) //[DWORD] // 1. Byte is the value //[DWORD] DPSG //= GSPD (Game Speed) //[DWORD] // 1. Byte is the value //[DWORD] HSSR //= RSSH (Resource Sharing) //[DWORD] // 1. Byte is the value //[DWORD] TRSR //= RSRT (Resource Rate) //[bYTE] // The Byte is the value chr = BinReader.ReadChars(4); str = new string(chr); if(counter == 7) { // Warum auch immer aber im letzen durchgang ist es eben nur ein byte wincondvalue = BinReader.ReadByte(); } else { wincondvalue = Convert.ToByte(BinReader.ReadInt32()); } switch (str) { case "FDIA": this.Replay.GameSetting.AIDiff = wincondvalue; break; case "TSSR": this.Replay.GameSetting.StartRes = wincondvalue; break; case "MTKL": this.Replay.GameSetting.LockTeams = wincondvalue; break; case "AEHC": this.Replay.GameSetting.CheatsON = wincondvalue; break; case "COLS": this.Replay.GameSetting.StartLocation = wincondvalue; break; case "DPSG": this.Replay.GameSetting.GameSpeed = wincondvalue; break; case "HSSR": this.Replay.GameSetting.ResShare = wincondvalue; break; case "TRSR": this.Replay.GameSetting.ResRate = wincondvalue; break; } } // this.Replay.NameStart = BinReader.BaseStream.Position; //[DWORD]->%length% // length of ingame Name num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte game name (%length% * 2) buffer = BinReader.ReadBytes(num * 2); this.Replay.Name = encoding1.GetString(buffer); //[DWORD] // End of Gamename Always 0 so far BinReader.ReadInt32(); //MessageBox.Show("Header Done..."); //[DWORD]->%length% // This is the number of Win conditions that are set. Each one is a constant DWORD that follows. counter = BinReader.ReadInt32(); // for(int win_count=0;win_count < counter;win_count++) { //{%length% win conditions} //[DWORD] // Win conditon //767227721 Annihilate //-1826760460 SuddenDeath //200405640 Assassinate //-242444938 EconomicVictory //735076042 ControlArea //1509920563 DestroyHQ //1959084950 TakeandHolde //69421273 GameTime num = BinReader.ReadInt32(); if(num.Equals(WinCondition.WinConditions.Annihilate)) // Annihilate { this.Replay.WinCondition.Annihilate = true; } else if(num.Equals(WinCondition.WinConditions.SuddenDeath)) // SuddenDeath { this.Replay.WinCondition.SuddenDeath = true; } else if(num.Equals(WinCondition.WinConditions.Assassinate)) // Assassinate { this.Replay.WinCondition.Assassinate = true; } else if(num.Equals(WinCondition.WinConditions.EconomicVictory)) // EconomicVictory { this.Replay.WinCondition.EconomicVictory = true; } else if(num.Equals(WinCondition.WinConditions.ControlArea)) // ControlArea { this.Replay.WinCondition.ControlArea = true; } else if(num.Equals(WinCondition.WinConditions.DestroyHQ)) // DestroyHQ { this.Replay.WinCondition.DestroyHQ = true; } else if(num.Equals(WinCondition.WinConditions.TakeandHolde)) // TakeandHolde { this.Replay.WinCondition.TakeandHolde = true; } else if(num.Equals(WinCondition.WinConditions.GameTime)) // GameTime { this.Replay.WinCondition.GameTime = true; } //{END Win Conditions} } this.Replay.PlayerStart = BinReader.BaseStream.Position; return true; } else { return false; } } #endregion #region ReadPlayer private void ReadPlayer() { this.Player = new Player(); //{Now for any Slot There come Player data //[KEYWORD](FOLDGPLY) // START OF NEW CHUNK - GPLY = Game Player? There is one of these chunks for each player. //MessageBox.Show("New Player at: " + BinReader.BaseStream.Position.ToString() + " (" + str + ")"); chr = BinReader.ReadChars(8); str = new string(chr); if(str.Equals("FOLDGPLY")) { //[DWORD] 2 // Chunk version (?) Always 2 so far BinReader.ReadInt32(); //[DWORD] 41330 // Chunk data size int zzz = BinReader.ReadInt32(); //MessageBox.Show(zzz.ToString() + " / " + BinReader.BaseStream.Position.ToString()); //[DWORD] 0 // A variable string length Always 0 so far BinReader.ReadInt32(); //[KEYWORD](DATAINFO) // START OF NEW CHUNK BinReader.ReadInt64(); //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 54 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // A variable string length Always 0 so far BinReader.ReadInt32(); //[DWORD]->%length% // length of Plyer Name num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte player name (%length% * 2) buffer = BinReader.ReadBytes(num * 2); this.Player.Name = encoding1.GetString(buffer); //[DWORD] // Type of Slot (0 Host/2 player/4 specc/7 empty/1,3,11 computer) this.Player.Type = BinReader.ReadInt32(); //[DWORD] // Team of the Player (counter starts with 0) this.Player.Team = (BinReader.ReadInt32() + 1); //[DWORD]->%length% // lengt of race string num = BinReader.ReadInt32(); //[TEXT] // Race Name (%length%) chr = BinReader.ReadChars(num); this.Player.Race = new string(chr); //[DWORD] // (?) Always 0 so far BinReader.ReadInt32(); // if(this.Replay.Version == 4) { //[DWORD] // (?) Always 0 so far num = BinReader.ReadInt32(); //[TEXT] // ? chr = BinReader.ReadChars(num); //After Version 1.5 there was this new Flag. //It is only present in Version > 1.5 //[bYTE] ??? byte tmp = BinReader.ReadByte(); if(tmp > 0) { BinReader.BaseStream.Seek(-1, System.IO.SeekOrigin.Current); } } // If there are Empty Slots don't read to fare for the last slot if(this.Player.Type != 7) { //[KEYWORD](FOLDTCUC) // START OF NEW CHUNK - TCUC = Team Colors, something, something? chr = BinReader.ReadChars(8); str = new string(chr); //{Also Possible: FOLDGPLY -> New Player beginns (happens with spec slots)} if(str == "FOLDTCUC") { // //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 84 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // A variable string length Always 0 so far BinReader.ReadInt32(); //[KEYWORD](DATALCIN) // START OF NEW CHUNK - LCIN = Local?? NOTE: This section differs for the replay's "local" player BinReader.ReadChars(8); //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD]->%length% 12 // Chunk data size num = BinReader.ReadInt32(); //[TEXT] // (?) (%length%) chr = BinReader.ReadChars(num); //[DWORD] // (?) Always 0 so far BinReader.ReadInt32(); //[KEYWORD](DATAUNCU) // START OF NEW CHUNK - UNCU = ???? BinReader.ReadChars(8); //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 32 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // A variable string length Always 0 so far BinReader.ReadInt32(); //[DWORD]->%length% // length of ColorScheme num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte scheme name (%length% * 2) buffer = BinReader.ReadBytes(num * 2); str = encoding1.GetString(buffer); //[DWORD] // Primary NOTE: Colors are BGRA (blue, green, red, alpha) buffer = BinReader.ReadBytes(4); this.Player.Primary = Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0]); //[DWORD] // Secondary buffer = BinReader.ReadBytes(4); this.Player.Secondary = Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0]); //[DWORD] // Trim buffer = BinReader.ReadBytes(4); this.Player.Trim = Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0]); //[DWORD] // Weapons buffer = BinReader.ReadBytes(4); this.Player.Weapons = Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0]); //[DWORD] // Eyes buffer = BinReader.ReadBytes(4); this.Player.Eyes = Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0]); // for(int bannerbadge=0; bannerbadge < 2 ;bannerbadge++) { //[KEYWORD](FOLDTCBD) // START OF NEW CHUNK - TCBD = Team color Badge? chr = BinReader.ReadChars(8); str = new string(chr); //{Also Possible: FOLDGPLY -> New Player beginns (happens sometimes...)} //{Another keyword FOLDTCBD -> Badge/Banner sometimes change their position in the Replay} //{If none of this Keywords is matchetd it seams like an broken header.. So ignore the 8byte keyword and go on with action data} // if(str.Equals("FOLDTCBD")) { //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 16460 // Chunk data size - A bigish chunk...hence the transferring badges lag BinReader.ReadInt32(); //[DWORD] 0 // A variable string length BinReader.ReadInt32(); //[KEYWORD](FOLDIMAG) // START OF NEW CHUNK - IMAG = Image (badge in this case) chr = BinReader.ReadChars(8); str = new string(chr); //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 16460 // Chunk data size BinReader.ReadInt32(); //[DWORD]->%length% // A variable string length num = BinReader.ReadInt32(); //{IF %length% GT 0 then the Badge has a special Name} //[TEXT] // Name of the Badge (%length%) chr = BinReader.ReadChars(num); str = new string(chr); //{END} //[KEYWORD](DATAATTR) // START OF NEW CHUNK - ATTR = Image attribute(s)? chr = BinReader.ReadChars(8); str = new string(chr); //[DWORD] 2 // Chunk version (?) Always 2 so far BinReader.ReadInt32(); //[DWORD] 16 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); //[DWORD] 64 // Image HEIGHT int height = BinReader.ReadInt32(); //[DWORD] 64 // Image WIDTH int width = BinReader.ReadInt32(); //[DWORD] 1 // (?) Always 1 so far BinReader.ReadInt32(); //[KEYWORD](DATADATA) // START OF NEW CHUNK - DATA = Image data? chr = BinReader.ReadChars(8); str = new string(chr); //[DWORD] 2 // Chunk version (?) Always 2 so far BinReader.ReadInt32(); //[DWORD] 16 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); // //{Now comes the image data. Pixel by pixel form heigth to width in RGBA colors - you also have to mirror the picture} this.Player.Badge = new Bitmap(width, height); for(int y =0;y<height;y++) { for(int x=0;x<width;x++) { buffer = BinReader.ReadBytes(4); this.Player.Badge.SetPixel(x, y, Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0])); } } this.Player.Badge.RotateFlip(RotateFlipType.Rotate180FlipX); // } else if(str.Equals("FOLDTCBN")) { // //[KEYWORD](FOLDTCBN) // START OF NEW CHUNK - TCBN = Team color Banner? //Already done //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 24652 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); //[KEYWORD](FOLDIMAG ) // START OF NEW CHUNK - IMAG = Image (banner in this case) BinReader.ReadInt64(); //[DWORD] 1 // Chunk version (?) Always 1 so far BinReader.ReadInt32(); //[DWORD] 1152 // Chunk data size BinReader.ReadInt32(); //[DWORD]->%length% // A variable string length num = BinReader.ReadInt32(); //{IF %length% GT 0 then the Banner has a special Name} //[TEXT] // Name of the Banner (%length%) chr = BinReader.ReadChars(num); str = new string(chr); //[KEYWORD](DATAATTR) // START OF NEW CHUNK - ATTR = Image attribute(s)? BinReader.ReadInt64(); //[DWORD] 2 // Chunk version (?) Always 2 so far BinReader.ReadInt32(); //[Word] 16 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); //[DWORD] 64 // Image WIDTH int width = BinReader.ReadInt32(); //[DWORD] 96 ´ // Image HEIGHT int height = BinReader.ReadInt32(); //[DWORD] 1 // (?) Always 1 so far BinReader.ReadInt32(); //[KEYWORD](DATADATA) // START OF NEW CHUNK - DATA = Image data? BinReader.ReadInt64(); //[DWORD] 2 // Chunk version (?) Always 2 so far BinReader.ReadInt32(); //[DWORD] 1536 // Chunk data size BinReader.ReadInt32(); //[DWORD] 0 // (?) BinReader.ReadInt32(); // //{Now comes the image data. Pixel by pixel form heigth to width in RGBA colors - you also have to turn the picture} this.Player.Banner = new Bitmap(width, height); for(int y =0;y<height;y++) { for(int x=0;x<width;x++) { buffer = BinReader.ReadBytes(4); this.Player.Banner.SetPixel(x, y, Color.FromArgb(buffer[3], buffer[2], buffer[1], buffer[0])); } } this.Player.Banner.RotateFlip(RotateFlipType.Rotate180FlipX); } else { //Header was not closed correct happend in some replays in <=1.3 //So just leave the loop an say we got a new Player bannerbadge = 2; BinReader.BaseStream.Seek(-8, System.IO.SeekOrigin.Current); } } } else { //Header was not closed correct happend in some replays in <=1.3 //So just leave the loop an say we got a new Player BinReader.BaseStream.Seek(-8, System.IO.SeekOrigin.Current); } } } else { //Header was not closed correct happend in some replays in <=1.3 //So just leave the loop an say we got a new Player BinReader.BaseStream.Seek(-8, System.IO.SeekOrigin.Current); } } // //{End of Player Data} //MessageBox.Show("Next Player at: " + BinReader.BaseStream.Position.ToString()); #endregion #region ReadActionDetail // Chat.SndID gleicher text wie bei actions private void ReadActionDetail() { //[DWORD] // Action Key - Action Key 1 identefies an Chat msg so far int Action1 = BinReader.ReadInt32(); //[DWORD] // Action Length int Action1Len = BinReader.ReadInt32(); int ReadedActionLen = 0; //MessageBox.Show(BinReader.BaseStream.Position.ToString() + " / " + Action1Len.ToString()); if(Action1.Equals(1)) { //[DWORD] // Action Key int Action2 = BinReader.ReadByte(); //{IF this action key is also 1 its an chat msg} if(Action2.Equals(1)) { // [bYTES] 3 empty Bytes BinReader.ReadBytes(3); this.Chat = new ChatMessage(); //[DWORD] // Length of Chat chunk BinReader.ReadInt32(); //[bYTE] // Also length of Chat chunk so far BinReader.ReadByte(); //{IF REP Version 1} if(this.Replay.Version == 1) { //[bYTE] // Player ID (As same orders as they are listed bevore) this.Chat.Snd = BinReader.ReadByte(); //[bYTE] // Reciver 0 All/1 Team/2 System this.Chat.Rec = BinReader.ReadByte(); //[bYTE] // (?) BinReader.ReadByte(); //{IF Rep Version 2} } else if(this.Replay.Version == 2 || this.Replay.Version == 4) { //MessageBox.Show(BinReader.BaseStream.Position.ToString()); //[DWORD]->%len% // Length of Player Name num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte player name (%len% * 2) buffer = BinReader.ReadBytes(num * 2); this.Chat.SndName = encoding1.GetString(buffer); //[DWORD] // Player ID > 1000 are normal players blow the IDs of the Spectators this.Chat.Snd = BinReader.ReadInt32(); //MessageBox.Show(this.Chat.Snd.ToString()); //[DWORD] // Msg Type 0 Player/1 Spec/2 System this.Chat.Type = BinReader.ReadInt32(); //[DWORD] // Reciver 0 All/1 Team/2 System this.Chat.Rec = BinReader.ReadInt32(); //{End of Version spec Data} } //[DWORD]->%len% // Length of Chat MSG num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte chat msg (%len% * 2) buffer = BinReader.ReadBytes(num * 2); this.Chat.Msg = encoding1.GetString(buffer); //{If second Action Key is not 1} this.Chat.Zeit = TickToTime(this.LastTick); this.Replay.ChatMSG.Add(this.Chat); } else if(Action2.Equals(Action1Len)) { // Here was a lag window //[DWORD]->%length% // length of player num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte player name (%len% * 2) buffer = BinReader.ReadBytes(num * 2); //[DWORD] // Player id num = BinReader.ReadInt32(); //[DWORD] ??? num = BinReader.ReadInt32(); //[DWORD] ??? num = BinReader.ReadInt32(); //[DWORD]->%length% // length of text num = BinReader.ReadInt32(); //[uNICODETEXT] // Double-byte text ??? (%len% * 2) buffer = BinReader.ReadBytes(num * 2); } else { // [bYTES] 3 empty bytes BinReader.ReadBytes(3); //[DWORD]->%length% // length of action num = BinReader.ReadInt32(); //[bYTE] // (?) BinReader.ReadByte(); //[TEXT] // (?) (%length%) BinReader.ReadBytes(num); //BinReader.ReadBytes(num); } //{If action key not is 1} } else if(Action1 == 0 && Action1Len > 14) { //[bYTE] ??? BinReader.ReadByte(); // Always 80 ?? if(this.Replay.Version > 1) { //[bYTE] Marks if action block contains actions BinReader.ReadByte(); // If 1 Action Block has action! } //[DWORD] Tick of this action int Tick = BinReader.ReadInt32(); this.LastTick = Tick; //[DWORD] Total number of actions so fare this.Replay.ActionCount = BinReader.ReadInt32(); // ActionCounter //[DWORD] ??? Move Data ? BinReader.ReadInt32(); // MoveData ReadedActionLen = 12; // 12 Bytes Already read //[DWORD] Length of the complete action part BinReader.ReadInt32(); //[bYTE] Single byte ??? BinReader.ReadByte(); ReadedActionLen = ReadedActionLen + 5; // Only read till action ends while(ReadedActionLen < Action1Len) { try { // Create new action Action CurrentAction = new Action(); //[bYTE] length of simple Action CurrentAction.ActionLen = BinReader.ReadByte(); ReadedActionLen = ReadedActionLen + 1; //// [bYTES] -> 14 ??? CurrentAction.ActionData = BinReader.ReadBytes(14); BinReader.BaseStream.Seek(-14, System.IO.SeekOrigin.Current); // [bYTE] always 0 BinReader.ReadByte(); // [WORD] ??? BinReader.ReadInt32(); // [WORD] Action kind CurrentAction.Kind = BinReader.ReadInt32(); // [WORD] Action kind value CurrentAction.KindValue = BinReader.ReadInt32(); // [bYTE] always 0 BinReader.ReadByte(); // [iNT16] Action Player after 1.3 startlocation + 1000 CurrentAction.ActionPlayer = BinReader.ReadInt16(); if(this.Replay.Version < 2 && this.Replay.Version > 1) { CurrentAction.ActionPlayer = (Int16) (CurrentAction.ActionPlayer + 1000); } // [iNT16] Action Player2 seams to be always the same CurrentAction.ActionPlayer2 = BinReader.ReadInt16(); // [iNT16] Action Player action counter for this player Int16 PlayerActionCounter = BinReader.ReadInt16(); // [bYTES] Action Data ??? length = ActionLen - 21 CurrentAction.ActionData2 = BinReader.ReadBytes(CurrentAction.ActionLen - 1 - 14 - 6); ReadedActionLen = ReadedActionLen + CurrentAction.ActionLen + 1; if(CurrentAction.ActionPlayer >= 1000) { //MessageBox.Show("Fehler ??? bei Sekunde: 1 " + CurrentAction.ActionPlayer.ToString()); //Add reades action to the player (not correct till now!) Player ThisActionPlayer = (this.Replay.Player[CurrentAction.ActionPlayer - 1000] as Player); //MessageBox.Show("Fehler ??? bei Sekunde: 2 " + CurrentAction.ActionPlayer.ToString()); ThisActionPlayer.ActionCount = PlayerActionCounter; ThisActionPlayer.LastActionTick = Tick; CurrentAction.Tick = Tick; CurrentAction.Second = System.Convert.ToInt16(Tick / 8); ThisActionPlayer.Actions.Add(CurrentAction); this.Replay.Player[CurrentAction.ActionPlayer - 1000] = ThisActionPlayer; } else{ //CurrentAction.Second = System.Convert.ToInt16(Tick / 8); //MessageBox.Show("Fehler ??? bei Sekunde: " + CurrentAction.Second.ToString() + " Player: " + CurrentAction.ActionPlayer.ToString()); } } catch { //MessageBox.Show("Fehler bei: " + BinReader.BaseStream.Position.ToString() + " Action: " + Action1.ToString() + " (" + Action1Len.ToString() + ")"); } } } else { BinReader.ReadBytes(Action1Len); // If empty action just read the action lenght //BinReader.ReadBytes(Action1Len); } } #endregion #region GetReplayInformation public bool GetReplayInformation(string FileName) { if(this.ReadHeader(FileName)) { for(int players = 0; players < this.Replay.Slots; players++) { this.ReadPlayer(); this.Replay.Player.Add(this.Player); } this.Cache.Add(FileName,this.Replay); this.BinReader.Close(); return true; } else { this.BinReader.Close(); return false; } } #endregion #region ReadReplay public bool ReadReplay(string FileName) { if(this.ReadReplayOnly(FileName)) { //Raise Event for Handlers this.ReplayChanged(this, EventArgs.Empty); return true; } else { return false; } } #endregion #region ReadReplayOnly public bool ReadReplayOnly(string FileName) { if(this.ReadHeader(FileName)) { if(this.Cache == null) { this.Cache = new Analyse(true); } //MessageBox.Show("Body Start..."); for(int players = 0; players < this.Replay.Slots; players++) { this.ReadPlayer(); this.Replay.Player.Add(this.Player); } this.Replay.ActionStart = BinReader.BaseStream.Position; //MessageBox.Show("Body Done..."); //MessageBox.Show("Actions Start... (AT:" + BinReader.BaseStream.Position.ToString() + ")"); //{Now The acion Data starts.. DoW is working with 8Ticks per second} while (BinReader.BaseStream.Position < BinReader.BaseStream.Length) { this.ReadActionDetail(); } //MessageBox.Show("Actions Done..."); this.BinReader.Close(); this.Cache.Add(FileName,this.Replay); return true; } else { this.BinReader.Close(); return false; } } #endregion } } "еще немного информации из инструмента debug менеджера AutoSave replays" recformat.txt by hype[NZ] ----------------------------------------------------------------------------- TYPE 0 CHUNK - EMPTY CHUNK ----------------------------------------------------------------------------- 00 00 00 00 \\ type 0 chunk... ordinary chunk 11 00 00 00 \\ length of chunk in this case length 17, implying empty chunk 50 \\ (P or 80) this is followed by the tick number 94 01 00 00 \\ tick number, starts at 01 00 00 00 6B 00 00 00 \\ number of actions performed in total. \\ Doesn't count messages. Is only incremented after an action \\ containing chunk has been performed, starts at 00 00 00 00 D6 78 72 72 \\ 'random' number generated during the game to calculate whether projectiles hit etc. 00 00 00 00 \\ no action - in this example this was an empty chunk ----------------------------------------------------------------------------- TYPE 1 CHUNK - MESSAGING AND CHAT ----------------------------------------------------------------------------- 01 00 00 00 \\ chunk begins, type 1 so chat 3F 00 00 00 \\ length of chunk = 63 01 00 00 00 \\ chat version 1 (haven't seen any others yet) 36 00 00 00 \\ length until end of chunk 36 \\ byte containing same info afaik 0A 00 00 00 \\ length of player name, 10 here l i n d e l o k s e \\ Player name, 10 double-bytes EA 03 00 00 \\ Player ID [00 00 00 00 is observer (?)] 00 00 00 00 \\ message type, 0 = player, 1 = observer, 2 = system 00 00 00 00 \\ receiver, 0 = all, 1 = team, 2 = system 07 00 00 00 \\ length of message, 7 here l i k e m e \\ message, 7 double-bytes ----------------------------------------------------------------------------- BUILD SCOUT - FULL CHUNK EXAMPLE ----------------------------------------------------------------------------- hype build scout Yao build scout \\ comments for Yao build scout 00 00 00 00 00 00 00 00 \\ type 0 3A 00 00 00 3A 00 00 00 \\ length 80 50 50 \\ P A5 01 00 00 51 00 00 00 \\ timestamp 81 03 00 00 00 00 00 00 00 \\ total actions so far, this is the first 83 5E 0E 32 67 B2 68 92 \\ 'random number' 01 00 00 00 01 00 00 00 \\ one action in this chunk 0F 9E 82 29 9B 6A 75 53 \\ true multiplayer ID (in skirmish this is 00 00 00 00) 00 00 00 00 00 00 00 00 \\ all actions seem to have this so far 1C 00 00 00 1C 00 00 00 \\ length remaining 1C 1C 00 1C 1C 00 \\ length remaining byte, length remaining 2byte B8 25 0D 00 B6 11 0D 00 \\ ??? depends on the player though (resources ???) 03 00 00 00 03 00 00 00 \\ build unit 73 73 \\ unit is scout 03 00 00 00 03 00 00 00 \\ build unit E9 03 E9 03 E8 03 E8 03 \\ player ID 00 00 00 00 \\ number of actions by player 01 02 01 02 \\ 01 is number of things selected \\ 02 refers to building (01 = builder ???, 02 = building, 03 = unit) 69 67 \\ this is ingame building ID 05 00 00 00 05 00 00 00 \\ ??? \\ footnote: it seems that the scout's ID is not assigned here, rather the game assigns newly built units a specific number based on order built \\ e.g. in a 1v1, \\ 1st player servitor would be 50 C3 00 00 (dec 50000) \\ 2nd player servitor would be 51 C3 00 00 (dec 50001) \\ next unit built would be 52 C3 00 00 (dec 50002) \\ etc ----------------------------------------------------------------------------- UNIT COMMANDS ----------------------------------------------------------------------------- > moving 4 squads around 2x scouts 2x tacs (subchunk only, west endian) 23 00 \\ length D2 36 61 00 \\ mystery number 00 00 00 00 \\ 00 \\ 00 00 00 00 \\ move command 9E 30 9E 30 \\ player ID 32 00 \\ number of player actions so far 40 \\ 4 squads selected 30:95 3C 00 00 \\ unit ID marker:unit ID 30:85 3C 00 00 30:55 3C 00 00 30:65 3C 00 00 10 \\ ??? 00 \\ move 3E EF \\ x ??? 82 00 \\ y ??? D4 EF \\ z ??? > perform action on target (e.g. squads will attack an enemy, resume building a structure etc) 12 00 0D 31 71 00 00 00 00 00 00 00 00 00 00 9E 30 9E 30 92 00 10 \\ 01 - 1 unit selected 30:95 3C 00 00 \\ unit ID ... builder is 60:xx xx xx xx 10 70 \\ ?? always 01 \\ 07 = perform action on target (e.g. attack, build, repair) ?? 57 50 00 00 ----------------------------------------------------------------------------- BUILDING STUFF ----------------------------------------------------------------------------- ------------------------------ GLOBAL STUFF ------------------------------ CANCEL UNIT SUBCHUNK Each building records the list of units started in order. The code to cancel is just the order in which the unit was started, even if it was started 20 mins later. e.g. serv scout scout scout scout-> cancel 3rd scout, wait 20 mins serv -> cancel serv cancel codes will be 05 and then 06 C1 00 \\length FB 3E 1B 10 \\ ??? (syncing ???) 40 00 00 00 \\ primary command (cancel unit) 10 \\ cancel first unit in queue, see note above 00 00 00 00 \\ secondary command (none) 8E 30 8E 30 \\ player ID (1001) 40 00 \\ player actions 10 20 \\ 01 selected, 02 = building (for SM) 76 \\ building ID (could be 76 50 00 00, which would imply the next byte 00 means 'nothing to follow' 50 00 00 00 \\ ??? --------------------------------- SPACE MARINE build stuff subchunk --------------------------------- (west endian) > SPACE MARINE - build structure subchunk 82 00 \\ length remaining AB D1 01 00 57 00 00 00 \\ servitor -> build something command (?) 97 \\ 77 = sacred artifact\\ 78 = armoury\\ 79 = rax\\ 7B = gen\\ 7C = HQ\\ 7F = lp \\ 80 = mines\\ 81 orbital relay\\ 86 Big Gen\\ 87 = turret\\ 89 = machine cult 20 00 00 00 \\ build building command (02) 8E 30 8E 30 00 00 \\ player actions so far 10 10 \\ 1 unit selected \\ builder 8E 30 00 00 \\ Player ID 20 60:05 3C 00 00 \\ servitor ID 00 0A 00 \\ 82 00 \\ 8B 10 \\ coordinates (x,y,?) > SPACE MARINE - build unit subchunk (space marine); C1 00 55 63 01 00 30 00 00 00 \\ build 67 \\ 52 = Apothecary \\ 54 = ASM \\ 5A = Chaplain \\ 5D = Dreadnaught \\ 63 = Force Commander \\ 65 = Grey Knight \\ 68 = Landraider \\ 69 = landspeeder \\ 6C = Librarian \\ 6F = predator \\ 71 = rhino \\ 73 = scout \\ 76 = servitor \\ 7C = tac \\ 7F = Terminators\\ 80 = Assault Terminators \\ 89 = whirlwind 30 00 00 00 \\ build unit command (03) 8E 30 8E 30 10 00 10 20 \\ 1 thing selected \\ 02 => building 76 \\ ingame building ID 50 00 00 00 > SPACE MARINE - addons (tier2, tier3, lp2, lp3 etc) C1 00 17 B0 B0 00 70 00 00 00 92 \\29 = SM tier 2 \\ 2A = SM tier 3 \\ 2B = SM lp2 \\ 2C = SM lp3 \\2E = missile turret addon C0 00 00 00 8E 30 8E 30 30 00 10 20 11 60 00 00 00 > SPACE MARINE - population increase C1 00 50 D4 70 00 60 00 00 00 AA \\ AA = inf_01 \\ AB = inf_02 \\ AC = inf_03 \\ AD = inf_04 \\ \\ AE = veh_01 \\ AF = veh_02 \\ B0 = veh_03 \\ B1 = veh_04 \\ E0 00 00 00 9E 30 9E 30 31 00 10 20 EA E0 00 00 00 > SPACE MARINE - wargear C1 00 2F F1 C0 00 60 00 00 00 \\ 70 00 00 00 for HQ wargear 70 \\ 07 = Power Sword + Pistol \\ 09 = Daemonhammer + Melta \\ 10 = Champion Cincture \\ 11 = Iron Halo \\ 12 = Bracers of the Righteous \\ 13 = Heroism Pedestal \\ 14 = Master Armour \\ 15 = Orbital Strike Wargear E0 00 00 00 \\ C0 00 00 00 for HQ wargear 9E 30 9E 30 F2 00 10 20 4B \\ building ingame ID E0 00 00 00 --------------------------- NECRON build stuff subchunk --------------------------- > NECRON - build unit C1 00 27 FE 94 00 30 00 00 00 2B \\ B2 = NW \\ B8 = Deceiver \\ BB = Destroyer \\ BD = Flayed \\BF = Heavy Destroyer \\ C1 = Immortal \\ C4 = Destroyer Lord \\ C5 = Necron Lord \\CB = Pariah \\ D7 = Wraith \\ 20 00 00 00 8E 30 8E 30 70 00 10 20 4A E0 00 00 00 > NECRON - build structure 82 00 86 9F E0 00 57 00 00 00 \\ builder unit build 6A \\ A2 = monolith \\ A6 = Forbidden archives \\ A5 = Energy Core \\ A7 = Greater Summoning Core \\ A8 = LP \\ A9 = Gen \\ AA = Summoning Core \\ AC = Thermogan \\ AE = turret \\ D5 = tombspyder 10 00 00 00 \\ Structure 9E 30 9E 30 00 00 10 10 9E 30 00 00 \\ player ID again 20 60:45 3C 00 00 00 45 10 B4 00 0D DF > NECRON - addons C1 00 EB D5 94 00 70 00 00 00 0D \\ D0 = Necron Tier 2 \\ D1 = Necron tier 3 \\ D3 = lp2 \\ D4 = lp3 \\ D5 = Gauss turret II \\ B0 00 00 00 8E 30 8E 30 50 00 10 20 31 \\ building ingame ID E0 00 00 00 \\ can be different, but the same throughout each rep > NECRON - wargear (similar to addons) C1 00 B9 A7 E4 00 70 00 00 00 B1 \\ 16 = Death Mask \\ 17 = Mantle of Doom \\ 18 = Extinction Claw \\ 19 = Herald of Woe \\ 1A = Raiment End of times \\ 1B = Heart of Darkness \\ C0 00 00 00 8E 30 8E 30 81 00 10 20 5B E0 00 00 00 C1 00 \\ from different rep (skirmish) F2 B0 20 00 60 00 00 00 C2 \\ 2A = NL weapon_01 \\ 2C = NL weaponn_02 E0 00 00 00 8E 30 8E 30 91 00 10 20 D7 50 00 00 00 > NECRON - Summon Nightbringer 12 00 45 B9 F4 00 35 00 00 00 \\ ability (?) 00 00 00 00 00 8E 30 8E 30 B1 00 10 \\ number of units selected 30:26 3C 00 00 \\ Necron Lord ingame ID 10 70 \\ ??? 1D E0 \\ ??? 00 00 \\ ??? ________________________________________________________________________________ _________________________________ COORDINATES: Blood River /|\ / | \ x -axis / | 2\ left crit/ o \right crit (origin) (1270,1270) \ | / \1 | / \ | /y-axis north A0 BF 38 00 6F 40 \\ 64266 \\ 131 \\ 1270 \\ east 6F 40 C2 10 6F 40 \\ 1270 \\ 300 \\ 1270 \\ south 6F 40 E3 00 A0 BF \\ 1270 \\ 62 \\ 64266 \\ west A0 BF 66 00 A0 BF \\ 64266 \\ 102 \\ 64266 \\ xx xx zz zz yy yy start at left crit and go clockwise, all these are just rally monolith note FB 0A + 04 F6 = FF FF + 1 left crit ** _________________ 32 00 E3 8A 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 00 00 10 20 CA 40 00 00 10 00 A0 BF 76 00 A0 BF 32 00 10 9A 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 10 00 10 20 CA 40 00 00 10 00 A0 BF 66 00 A0 BF 32 00 0E 9A 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 20 00 10 20 CA 40 00 00 10 00 A0 BF 66 00 A0 BF 32 00 4B AA 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 30 00 10 20 CA 40 00 00 10 00 A0 BF 66 00 A0 BF 32 00 DA BA 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 40 00 10 20 CA 40 00 00 10 00 A0 BF 66 00 A0 BF top ** 32 00 6D BC 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 60 00 10 20 CA 40 00 00 10 00 A0 BF 38 00 6F 40 32 00 1D CC 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 70 00 10 20 CA 40 00 00 10 00 A0 BF 38 00 6F 40 32 00 BD DC 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 80 00 10 20 CA 40 00 00 10 00 A0 BF 38 00 6F 40 32 00 6C EC 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 90 00 10 20 CA 40 00 00 10 00 A0 BF 38 00 6F 40 right crit ** 32 00 37 1E 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 A0 00 10 20 CA 40 00 00 10 00 6F 40 C2 10 6F 40 32 00 64 2E 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 B0 00 10 20 CA 40 00 00 10 00 6F 40 C2 10 6F 40 32 00 02 3E 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 C0 00 10 20 CA 40 00 00 10 00 6F 40 C2 10 6F 40 32 00 90 4E 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 D0 00 10 20 CA 40 00 00 10 00 6F 40 C2 10 6F 40 32 00 8F 4E 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 E0 00 10 20 CA 40 00 00 10 00 6F 40 C2 10 6F 40 harbour ** 32 00 5A 8F 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 F0 00 10 20 CA 40 00 00 10 00 6F 40 F3 00 A0 BF 32 00 09 9F 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 01 00 10 20 CA 40 00 00 10 00 6F 40 E3 00 A0 BF 32 00 26 AF 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 11 00 10 20 CA 40 00 00 10 00 6F 40 E3 00 A0 BF 32 00 65 BF 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 21 00 10 20 CA 40 00 00 10 00 6F 40 E3 00 A0 BF 32 00 E4 CF 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 31 00 10 20 CA 40 00 00 10 00 6F 40 E3 00 A0 BF 32 00 E4 CF 3C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 31 00 10 20 CA 40 00 00 10 00 6F 40 E3 00 A0 BF left crit going clockwise sort of 32 00 F2 A1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 41 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 E2 BF 32 00 3D A1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 51 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 F2 BF 32 00 DA B1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 61 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 B2 BF 32 00 79 C1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 71 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 A2 BF 32 00 E4 D1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 81 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 D4 BF 32 00 5F D1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 91 00 10 20 CA 40 00 00 10 00 A0 BF 72 00 44 CF 32 00 DA E1 4C 00 E0 00 00 00 00 00 00 00 00 9E 30 9E 30 A1 00 10 20 CA 40 00 00 10 00 A0 BF 82 00 32 CF Изменено 12 апреля, 2011 пользователем USSRxAZ Ссылка на комментарий Поделиться на другие сайты Поделиться
_Master_ Опубликовано 12 апреля, 2011 Жалоба Поделиться Опубликовано 12 апреля, 2011 Где же перенесенная дискуссия? Ссылка на комментарий Поделиться на другие сайты Поделиться
USSRxAZ Опубликовано 12 апреля, 2011 Автор Жалоба Поделиться Опубликовано 12 апреля, 2011 пускай там лежит, там не так много конкретных утверждений Ссылка на комментарий Поделиться на другие сайты Поделиться
переведунец Опубликовано 12 апреля, 2011 Жалоба Поделиться Опубликовано 12 апреля, 2011 (изменено) Где же перенесенная дискуссия? А самому слабо? 2 Ламо: Вот тут достаточно всяких примеров. Постарайся обойтись без ссылок на г@вносайты. Кидай реплеи с синками без обсов (желательно сразу все одним архивом), или с синками во время простоя прямо сюда. 2 Морти: Считаю, что разрыв цепочки можно восстановить, исключив выпавшее звено. Похоже с первого раза смысл написанного Морталесом до тебя не дошел. Бывает, но чтобы продолжать дискуссию, тебе необходимо перечитать его сообщение, еще и еще, пока не придет озарение. Даже без обсов синк эроры происходят. Почему-то они чаще всего происходят, когда в играх есть некроны. Синки связаны не непосредственно с обсами, а со сворачиваниями игры. Но естественно, что сворачиваются прежде всего обсы. А что касается некронов - видимо в силу особенностей геймплея, они находят время и посредине игры свернуться без ушерба для контроля. Изменено 12 апреля, 2011 пользователем переведунец Ссылка на комментарий Поделиться на другие сайты Поделиться
_Master_ Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 Если ничего не делать - ничего и не получится. Хотелось бы узнать для начала - как изменяется структура реплея при синк эрроре. Т. е. на каком временном интервале выпадает синхронизация игровых событий? (Для того, чтобы понимать объем работ по посстановлению реплея). Считаю, что синхронизация - дело восстановимое, иначе бы игра он-лайн также останавливалась при критическом альт-табе :) Значит остается поднять механизм самовосстановления и использовать его в деле восстановления реплеев. Да вообще, считаю, что методов решения различных программных задач множество! Даже не стану приводить примеры - сами все знаете. Надо работать над тем как ЭТО СДЕЛАТЬ, а не "всё потеряно! Мы все умрём!" Ссылка на комментарий Поделиться на другие сайты Поделиться
переведунец Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 Хотелось бы узнать для начала - как изменяется структура реплея при синк эрроре. Ты сам с собой чтоли разговариваешь? Тебе уже несколько раз объяснили это, и если ты не можешь понять настолько элементарных вещей, то не стоит даже грезить о восстановлении своими силами. Все что мы можем сами - это попробовать разобраться в точных причинах ошибок, и если ты хочешь помочь, то будь добр, закинь сюда примеры реплеев о которых ты столько говорил. Ссылка на комментарий Поделиться на другие сайты Поделиться
_Master_ Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 Все объяснения заключались в неподтвержденных суждениях. Я бы хотел разобраться в бинарном коде. Реплеи нужно сначала найти (просмотрев их как минимум и убедившись, что они без обсов), затем выложить. Это как минимум вечером. Далее. Хотелось бы поработать с самим менеджером реплеев, т. к. мне мало что дает его исходный код (я не силен в программировании). Ссылка на комментарий Поделиться на другие сайты Поделиться
USSRxAZ Опубликовано 13 апреля, 2011 Автор Жалоба Поделиться Опубликовано 13 апреля, 2011 я изза этих разговоров почти дописал скрипт аплоада реплея на сайт) Ссылка на комментарий Поделиться на другие сайты Поделиться
mortales Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 (изменено) Считаю, что синхронизация - дело восстановимое, иначе бы игра он-лайн также останавливалась при критическом альт-табе Нет, онлайн игры не при чем. Там ты от сервера все данные получаешь, при чем здесь альт-таб? А тут другая ситуация. Задача, которую ты хочешь решить, мне представляется следующим образом: сначала я зашел в лабиринт, потом повернул налево, потом направо, потом вошел во 2-ой туннель слева, потом опять повернул направо, потом я час блуждал по лабиринту, не запоминая, куда я сворачиваю, потом я покурил у стенки с изображением древнего человека и от того места пошел налево-направо-направо и т. д. Задача: выбраться из лабиринта. Давайте просто восстановим как-нибудь тот фрагмент, когда я блуждал, выбирая случайные направления и не запоминая их, и все будет ок. А если что-то не сойдется, я буду попадать в забавные тупики. Изменено 13 апреля, 2011 пользователем mortales Ссылка на комментарий Поделиться на другие сайты Поделиться
_Master_ Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 (изменено) я изза этих разговоров почти дописал скрипт аплоада реплея на сайт)А это поможет делу восстановления реплеев? если что-то не сойдется, я буду попадать в забавные тупикиЛучше попадать в забавные тупики, чем сесть где остановился и умереть. [ Добавлено спустя 8 минут 29 секунд ] И еще мне не понятно, действительно ли игрой управляет сервер? По моим наблюдениям создаются пиринговые связи между компьютерами игроков. Это видно из консоли, где коннект создается со всеми игроками, косвенно это подтверждает пропорциональное увеличение потока данных в зависимости от количества присутствующих. Если связи пиринговые - то сервер нужен лишь для ведения статистики и возврата в общий чат. Поправьте, если я не прав. Изменено 13 апреля, 2011 пользователем _Master_ Ссылка на комментарий Поделиться на другие сайты Поделиться
USSRxAZ Опубликовано 13 апреля, 2011 Автор Жалоба Поделиться Опубликовано 13 апреля, 2011 А это поможет делу восстановления реплеев? я и не собирался этого делать Ссылка на комментарий Поделиться на другие сайты Поделиться
mortales Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 (изменено) В ДоВ - пиринговые, наверное, а вообще это от игры зависит. Но и не в этом дело. Лучше попадать в забавные тупики, чем сесть где остановился и умереть Это была ирония, при чем тут "остановиться и умереть"? Напишу яснее: если я прав, восстановить реплей невозможно. Изменено 13 апреля, 2011 пользователем mortales Ссылка на комментарий Поделиться на другие сайты Поделиться
_Master_ Опубликовано 13 апреля, 2011 Жалоба Поделиться Опубликовано 13 апреля, 2011 Кроме того напомню про возможность создания собственных серверов (в меню есть такая функция). [ Добавлено спустя 14 минуты 2 секунды ] при чем тут "остановиться и умереть"Это тоже была ирония. Напишу яснее: чтобы выяснить прав ты или не прав, нужно хоть что-то попытаться реализовать. Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения
Пожалуйста, войдите, чтобы комментировать
Вы сможете оставить комментарий после входа в
Войти