WWCompression.cs (30518B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // The Command & Conquer Map Editor and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // The Command & Conquer Map Editor and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 using System; 16 17 namespace MobiusEditor.Utility 18 { 19 /// <summary> 20 /// This class contains encoders and decoders for the Westwood XOR Delta and LCW compression schemes. 21 /// </summary> 22 public static class WWCompression 23 { 24 //////////////////////////////////////////////////////////////////////////////// 25 // Notes 26 //////////////////////////////////////////////////////////////////////////////// 27 // 28 // LCW streams should always start and end with the fill command (& 0x80) though 29 // the decompressor doesn't strictly require that it start with one the ability 30 // to use the offset commands in place of the RLE command early in the stream 31 // relies on it. Streams larger than 64k that need the relative versions of the 32 // 3 and 5 byte commands should start with a null byte before the first 0x80 33 // command to flag that they are relative compressed. 34 // 35 // LCW uses the following rules to decide which command to use: 36 // 1. Runs of the same colour should only use 4 byte RLE command if longer than 37 // 64 bytes. 2 and 3 byte offset commands are more efficient otherwise. 38 // 2. Runs of less than 3 should just be stored as is with the one byte fill 39 // command. 40 // 3. Runs greater than 10 or if the relative offset is greater than 41 // 4095 use an absolute copy. Less than 64 bytes uses 3 byte command, else it 42 // uses the 5 byte command. 43 // 4. If Absolute rule isn't met then copy from a relative offset with 2 byte 44 // command. 45 // 46 // Absolute LCW can efficiently compress data that is 64k in size, much greater 47 // and relative offsets for the 3 and 5 byte commands are needed. 48 // 49 // The XOR delta generator code works to the following assumptions 50 // 51 // 1. Any skip command is preferable if source and base are same 52 // 2. Fill is preferable to XOR if 4 or larger, XOR takes same data plus at 53 // least 1 byte 54 // 55 //////////////////////////////////////////////////////////////////////////////// 56 57 //////////////////////////////////////////////////////////////////////////////// 58 // Some defines used by the encoders 59 //////////////////////////////////////////////////////////////////////////////// 60 public const Byte XOR_SMALL = 0x7F; 61 public const Byte XOR_MED = 0xFF; 62 public const Int32 XOR_LARGE = 0x3FFF; 63 public const Int32 XOR_MAX = 0x7FFF; 64 65 //////////////////////////////////////////////////////////////////////////////// 66 // Some utility functions to get worst case sizes for buffer allocation 67 //////////////////////////////////////////////////////////////////////////////// 68 69 public static Int32 LCWWorstCase(Int32 datasize) 70 { 71 return datasize + (datasize / 63) + 1; 72 } 73 74 public static Int32 XORWorstCase(Int32 datasize) 75 { 76 return datasize + ((datasize / 63) * 3) + 4; 77 } 78 79 /// <summary> 80 /// Compresses data to the proprietary LCW format used in 81 /// many games developed by Westwood Studios. Compression is better 82 /// than that achieved by popular community tools. This is a new 83 /// implementation based on understanding of the compression gained from 84 /// the reference code. 85 /// </summary> 86 /// <param name="input">Array of the data to compress.</param> 87 /// <returns>The compressed data.</returns> 88 /// <remarks>Commonly known in the community as "format80".</remarks> 89 public static Byte[] LcwCompress(Byte[] input) 90 { 91 if (input == null || input.Length == 0) 92 return new Byte[0]; 93 94 //Decide if we are going to do relative offsets for 3 and 5 byte commands 95 Boolean relative = input.Length > UInt16.MaxValue; 96 97 // Nyer's C# conversion: replacements for write and read for pointers. 98 Int32 getp = 0; 99 Int32 putp = 0; 100 // Input length. Used commonly enough to warrant getting it out in advance I guess. 101 Int32 getend = input.Length; 102 // "Worst case length" code by OmniBlade. We'll just use a buffer of 103 // that max length and cut it down to the actual used size at the end. 104 // Not using it- it's not big enough in case of some small images. 105 //LCWWorstCase(getend) 106 Int32 worstcase = Math.Max(10000, getend * 2); 107 Byte[] output = new Byte[worstcase]; 108 // relative LCW starts with 0 as flag to decoder. 109 // this is only used by later games for decoding hi-color vqa files. 110 if (relative) 111 output[putp++] = 0; 112 113 //Implementations that properly conform to the WestWood encoder should 114 //write a starting cmd1. It's important for using the offset copy commands 115 //to do more efficient RLE in some cases than the cmd4. 116 117 //we also set bool to flag that we have an on going cmd1. 118 Int32 cmd_onep = putp; 119 output[putp++] = 0x81; 120 output[putp++] = input[getp++]; 121 Boolean cmd_one = true; 122 123 //Compress data until we reach end of input buffer. 124 while (getp < getend) 125 { 126 //Is RLE encode (4bytes) worth evaluating? 127 if (getend - getp > 64 && input[getp] == input[getp + 64]) 128 { 129 //RLE run length is encoded as a short so max is UINT16_MAX 130 Int32 rlemax = (getend - getp) < UInt16.MaxValue ? getend : getp + UInt16.MaxValue; 131 Int32 rlep = getp + 1; 132 while (rlep < rlemax && input[rlep] == input[getp]) 133 rlep++; 134 135 UInt16 run_length = (UInt16)(rlep - getp); 136 137 //If run length is long enough, write the command and start loop again 138 if (run_length >= 0x41) 139 { 140 //write 4byte command 0b11111110 141 cmd_one = false; 142 output[putp++] = 0xFE; 143 output[putp++] = (Byte)(run_length & 0xFF); 144 output[putp++] = (Byte)((run_length >> 8) & 0xFF); 145 output[putp++] = input[getp]; 146 getp = rlep; 147 continue; 148 } 149 } 150 151 //current block size for an offset copy 152 UInt16 block_size = 0; 153 //Set where we start looking for matching runs. 154 Int32 offstart = relative ? getp < UInt16.MaxValue ? 0 : getp - UInt16.MaxValue : 0; 155 156 //Look for matching runs 157 Int32 offchk = offstart; 158 Int32 offsetp = getp; 159 while (offchk < getp) 160 { 161 //Move offchk to next matching position 162 while (offchk < getp && input[offchk] != input[getp]) 163 offchk++; 164 165 //If the checking pointer has reached current pos, break 166 if (offchk >= getp) 167 break; 168 169 //find out how long the run of matches goes for 170 Int32 i; 171 for (i = 1; getp + i < getend; ++i) 172 if (input[offchk + i] != input[getp + i]) 173 break; 174 if (i >= block_size) 175 { 176 block_size = (UInt16)i; 177 offsetp = offchk; 178 } 179 offchk++; 180 } 181 182 //decide what encoding to use for current run 183 //If it's less than 2 bytes long, we store as is with cmd1 184 if (block_size <= 2) 185 { 186 //short copy 0b10?????? 187 //check we have an existing 1 byte command and if its value is still 188 //small enough to handle additional bytes 189 //start a new command if current one doesn't have space or we don't 190 //have one to continue 191 if (cmd_one && output[cmd_onep] < 0xBF) 192 { 193 //increment command value 194 output[cmd_onep]++; 195 output[putp++] = input[getp++]; 196 } 197 else 198 { 199 cmd_onep = putp; 200 output[putp++] = 0x81; 201 output[putp++] = input[getp++]; 202 cmd_one = true; 203 } 204 //Otherwise we need to decide what relative copy command is most efficient 205 } 206 else 207 { 208 Int32 offset; 209 Int32 rel_offset = getp - offsetp; 210 if (block_size > 0xA || ((rel_offset) > 0xFFF)) 211 { 212 //write 5 byte command 0b11111111 213 if (block_size > 0x40) 214 { 215 output[putp++] = 0xFF; 216 output[putp++] = (Byte)(block_size & 0xFF); 217 output[putp++] = (Byte)((block_size >> 8) & 0xFF); 218 //write 3 byte command 0b11?????? 219 } 220 else 221 { 222 output[putp++] = (Byte)((block_size - 3) | 0xC0); 223 } 224 225 offset = relative ? rel_offset : offsetp; 226 //write 2 byte command? 0b0??????? 227 } 228 else 229 { 230 offset = rel_offset << 8 | (16 * (block_size - 3) + (rel_offset >> 8)); 231 } 232 output[putp++] = (Byte)(offset & 0xFF); 233 output[putp++] = (Byte)((offset >> 8) & 0xFF); 234 getp += block_size; 235 cmd_one = false; 236 } 237 } 238 239 //write final 0x80, basically an empty cmd1 to signal the end of the stream. 240 output[putp++] = 0x80; 241 242 Byte[] finalOutput = new Byte[putp]; 243 Array.Copy(output, 0, finalOutput, 0, putp); 244 // Return the final compressed data. 245 return finalOutput; 246 } 247 248 /// <summary> 249 /// Decompresses data in the proprietary LCW format used in many games 250 /// developed by Westwood Studios. 251 /// </summary> 252 /// <param name="input">The data to decompress.</param> 253 /// <param name="readOffset">Location to start at in the input array.</param> 254 /// <param name="output">The buffer to store the decompressed data. This is assumed to be initialized to the correct size.</param> 255 /// <param name="readEnd">End offset for reading. Use 0 to take the end of the given data array.</param> 256 /// <returns>Length of the decompressed data in bytes.</returns> 257 public static Int32 LcwDecompress(Byte[] input, ref Int32 readOffset, Byte[] output, Int32 readEnd) 258 { 259 if (input == null || input.Length == 0 || output == null || output.Length == 0) 260 return 0; 261 Boolean relative = false; 262 // Nyer's C# conversion: replacements for write and read for pointers. 263 Int32 writeOffset = 0; 264 // Output length should be part of the information given in the file format using LCW. 265 // Techncically it can just be cropped at the end, though this value is used to 266 // automatically cut off repeat-commands that go too far. 267 Int32 writeEnd = output.Length; 268 if (readEnd <= 0) 269 readEnd = input.Length; 270 271 //Decide if the stream uses relative 3 and 5 byte commands 272 //Extension allows effective compression of data > 64k 273 //https://github.com/madmoose/scummvm/blob/bladerunner/engines/bladerunner/decompress_lcw.cpp 274 // this is only used by later games for decoding hi-color vqa files. 275 // For other stuff (like shp), just check in advance to decide if the data is too big. 276 if (readOffset >= readEnd) 277 return writeOffset; 278 if (input[readOffset] == 0) 279 { 280 relative = true; 281 readOffset++; 282 } 283 //DEBUG_SAY("LCW Decompression... \n"); 284 while (writeOffset < writeEnd) 285 { 286 if (readOffset >= readEnd) 287 return writeOffset; 288 Byte flag = input[readOffset++]; 289 UInt16 cpysize; 290 UInt16 offset; 291 292 if ((flag & 0x80) != 0) 293 { 294 if ((flag & 0x40) != 0) 295 { 296 cpysize = (UInt16)((flag & 0x3F) + 3); 297 //long set 0b11111110 298 if (flag == 0xFE) 299 { 300 if (readOffset >= readEnd) 301 return writeOffset; 302 cpysize = input[readOffset++]; 303 if (readOffset >= readEnd) 304 return writeOffset; 305 cpysize += (UInt16)((input[readOffset++]) << 8); 306 if (cpysize > writeEnd - writeOffset) 307 cpysize = (UInt16)(writeEnd - writeOffset); 308 if (readOffset >= readEnd) 309 return writeOffset; 310 //DEBUG_SAY("0b11111110 Source Pos %ld, Dest Pos %ld, Count %d\n", source - sstart - 3, dest - start, cpysize); 311 for (; cpysize > 0; --cpysize) 312 { 313 if (writeOffset >= writeEnd) 314 return writeOffset; 315 output[writeOffset++] = input[readOffset]; 316 } 317 readOffset++; 318 } 319 else 320 { 321 Int32 s; 322 //long move, abs 0b11111111 323 if (flag == 0xFF) 324 { 325 if (readOffset >= readEnd) 326 return writeOffset; 327 cpysize = input[readOffset++]; 328 if (readOffset >= readEnd) 329 return writeOffset; 330 cpysize += (UInt16)((input[readOffset++]) << 8); 331 if (cpysize > writeEnd - writeOffset) 332 cpysize = (UInt16)(writeEnd - writeOffset); 333 if (readOffset >= readEnd) 334 return writeOffset; 335 offset = input[readOffset++]; 336 if (readOffset >= readEnd) 337 return writeOffset; 338 offset += (UInt16)((input[readOffset++]) << 8); 339 //extended format for VQA32 340 if (relative) 341 s = writeOffset - offset; 342 else 343 s = offset; 344 //DEBUG_SAY("0b11111111 Source Pos %ld, Dest Pos %ld, Count %d, Offset %d\n", source - sstart - 5, dest - start, cpysize, offset); 345 for (; cpysize > 0; --cpysize) 346 { 347 if (writeOffset >= writeEnd) 348 return writeOffset; 349 output[writeOffset++] = output[s++]; 350 } 351 //short move abs 0b11?????? 352 } 353 else 354 { 355 if (cpysize > writeEnd - writeOffset) 356 cpysize = (UInt16)(writeEnd - writeOffset); 357 if (readOffset >= readEnd) 358 return writeOffset; 359 offset = input[readOffset++]; 360 if (readOffset >= readEnd) 361 return writeOffset; 362 offset += (UInt16)((input[readOffset++]) << 8); 363 //extended format for VQA32 364 if (relative) 365 s = writeOffset - offset; 366 else 367 s = offset; 368 //DEBUG_SAY("0b11?????? Source Pos %ld, Dest Pos %ld, Count %d, Offset %d\n", source - sstart - 3, dest - start, cpysize, offset); 369 for (; cpysize > 0; --cpysize) 370 { 371 if (writeOffset >= writeEnd) 372 return writeOffset; 373 output[writeOffset++] = output[s++]; 374 } 375 } 376 } 377 //short copy 0b10?????? 378 } 379 else 380 { 381 if (flag == 0x80) 382 { 383 //DEBUG_SAY("0b10?????? Source Pos %ld, Dest Pos %ld, Count %d\n", source - sstart - 1, dest - start, 0); 384 return writeOffset; 385 } 386 cpysize = (UInt16)(flag & 0x3F); 387 if (cpysize > writeEnd - writeOffset) 388 cpysize = (UInt16)(writeEnd - writeOffset); 389 //DEBUG_SAY("0b10?????? Source Pos %ld, Dest Pos %ld, Count %d\n", source - sstart - 1, dest - start, cpysize); 390 for (; cpysize > 0; --cpysize) 391 { 392 if (readOffset >= readEnd || writeOffset >= writeEnd) 393 return writeOffset; 394 output[writeOffset++] = input[readOffset++]; 395 } 396 } 397 //short move rel 0b0??????? 398 } 399 else 400 { 401 cpysize = (UInt16)((flag >> 4) + 3); 402 if (cpysize > writeEnd - writeOffset) 403 cpysize = (UInt16)(writeEnd - writeOffset); 404 if (readOffset >= readEnd) 405 return writeOffset; 406 offset = (UInt16)(((flag & 0xF) << 8) + input[readOffset++]); 407 //DEBUG_SAY("0b0??????? Source Pos %ld, Dest Pos %ld, Count %d, Offset %d\n", source - sstart - 2, dest - start, cpysize, offset); 408 for (; cpysize > 0; --cpysize) 409 { 410 if (writeOffset >= writeEnd || writeOffset < offset) 411 return writeOffset; 412 output[writeOffset] = output[writeOffset - offset]; 413 writeOffset++; 414 } 415 } 416 } 417 // If buffer is full, make sure to skip end command! 418 if (writeOffset == writeEnd && readOffset < input.Length && input[readOffset] == 0x80) 419 readOffset++; 420 return writeOffset; 421 } 422 423 /// <summary> 424 /// Generates a binary delta between two buffers. Mainly used for image data. 425 /// </summary> 426 /// <param name="source">Buffer containing data to generate the delta for.</param> 427 /// <param name="base">Buffer containing data that is the base for the delta.</param> 428 /// <returns>The generated delta as bytes array.</returns> 429 /// <remarks>Commonly known in the community as "format40".</remarks> 430 public static Byte[] GenerateXorDelta(Byte[] source, Byte[] @base) 431 { 432 // Nyer's C# conversion: replacements for write and read for pointers. 433 // -for our delta (output) 434 Int32 putp = 0; 435 // -for the image we go to 436 Int32 getsp = 0; 437 // -for the image we come from 438 Int32 getbp = 0; 439 //Length to process 440 Int32 getsendp = Math.Min(source.Length, @base.Length); 441 Byte[] dest = new Byte[XORWorstCase(getsendp)]; 442 443 //Only check getsp to save a redundant check. 444 //Both source and base should be same size and both pointers should be 445 //incremented at the same time. 446 while (getsp < getsendp) 447 { 448 UInt32 fillcount = 0; 449 UInt32 xorcount = 0; 450 UInt32 skipcount = 0; 451 Byte lastxor = (Byte)(source[getsp] ^ @base[getbp]); 452 Int32 testsp = getsp; 453 Int32 testbp = getbp; 454 455 //Only evaluate other options if we don't have a matched pair 456 while (testsp < getsendp && source[testsp] != @base[testbp]) 457 { 458 if ((source[testsp] ^ @base[testbp]) == lastxor) 459 { 460 ++fillcount; 461 ++xorcount; 462 } 463 else 464 { 465 if (fillcount > 3) 466 break; 467 lastxor = (Byte)(source[testsp] ^ @base[testbp]); 468 fillcount = 1; 469 ++xorcount; 470 } 471 testsp++; 472 testbp++; 473 } 474 475 //fillcount should always be lower than xorcount and should be greater 476 //than 3 to warrant using the fill commands. 477 fillcount = fillcount > 3 ? fillcount : 0; 478 479 //Okay, lets see if we have any xor bytes we need to handle 480 xorcount -= fillcount; 481 while (xorcount != 0) 482 { 483 UInt16 count; 484 //It's cheaper to do the small cmd twice than do the large cmd once 485 //for data that can be handled by two small cmds. 486 //cmd 0??????? 487 if (xorcount < XOR_MED) 488 { 489 count = (UInt16)(xorcount <= XOR_SMALL ? xorcount : XOR_SMALL); 490 dest[putp++] = (Byte)count; 491 //cmd 10000000 10?????? ?????? 492 } 493 else 494 { 495 count = (UInt16)(xorcount <= XOR_LARGE ? xorcount : XOR_LARGE); 496 dest[putp++] = 0x80; 497 dest[putp++] = (Byte)(count & 0xFF); 498 dest[putp++] = (Byte)(((count >> 8) & 0xFF) | 0x80); 499 } 500 501 while (count != 0) 502 { 503 dest[putp++] = (Byte)(source[getsp++] ^ @base[getbp++]); 504 count--; 505 xorcount--; 506 } 507 } 508 509 //lets handle the bytes that are best done as xorfill 510 while (fillcount != 0) 511 { 512 UInt16 count; 513 //cmd 00000000 ???????? 514 if (fillcount <= XOR_MED) 515 { 516 count = (UInt16)fillcount; 517 dest[putp++] = 0; 518 dest[putp++] = (Byte)(count & 0xFF); 519 //cmd 10000000 11?????? ?????? 520 } 521 else 522 { 523 count = (UInt16)(fillcount <= XOR_LARGE ? fillcount : XOR_LARGE); 524 dest[putp++] = 0x80; 525 dest[putp++] = (Byte)(count & 0xFF); 526 dest[putp++] = (Byte)(((count >> 8) & 0xFF) | 0xC0); 527 } 528 dest[putp++] = (Byte)(source[getsp] ^ @base[getbp]); 529 fillcount -= count; 530 getsp += count; 531 getbp += count; 532 } 533 534 //Handle regions that match exactly 535 while (testsp < getsendp && source[testsp] == @base[testbp]) 536 { 537 skipcount++; 538 testsp++; 539 testbp++; 540 } 541 542 while (skipcount != 0) 543 { 544 UInt16 count; 545 //Again it's cheaper to do the small cmd twice than do the large cmd 546 //once for data that can be handled by two small cmds. 547 //cmd 1??????? 548 if (skipcount < XOR_MED) 549 { 550 count = (Byte)(skipcount <= XOR_SMALL ? skipcount : XOR_SMALL); 551 dest[putp++] = (Byte)(count | 0x80); 552 //cmd 10000000 0??????? ???????? 553 } 554 else 555 { 556 count = (UInt16)(skipcount <= XOR_MAX ? skipcount : XOR_MAX); 557 dest[putp++] = 0x80; 558 dest[putp++] = (Byte)(count & 0xFF); 559 dest[putp++] = (Byte)((count >> 8) & 0xFF); 560 } 561 skipcount -= count; 562 getsp += count; 563 getbp += count; 564 } 565 } 566 567 //final skip command of 0 to signal end of stream. 568 dest[putp++] = 0x80; 569 dest[putp++] = 0; 570 dest[putp++] = 0; 571 572 Byte[] finalOutput = new Byte[putp]; 573 Array.Copy(dest, 0, finalOutput, 0, putp); 574 // Return the final data 575 return finalOutput; 576 } 577 578 /// <summary> 579 /// Applies a binary delta to a buffer. 580 /// </summary> 581 /// <param name="data">The data to apply the xor to.</param> 582 /// <param name="xorSource">The the delta data to apply.</param> 583 /// <param name="xorStart">Start offset in the data.</param> 584 /// <param name="xorEnd">End offset in the data. Use 0 to take the end of the whole array.</param> 585 public static void ApplyXorDelta(Byte[] data, Byte[] xorSource, ref Int32 xorStart, Int32 xorEnd) 586 { 587 // Nyer's C# conversion: replacements for write and read for pointers. 588 Int32 putp = 0; 589 Byte value = 0; 590 Int32 dataEnd = data.Length; 591 if (xorEnd <= 0) 592 xorEnd = xorSource.Length; 593 while (putp < dataEnd && xorStart < xorEnd) 594 { 595 //DEBUG_SAY("XOR_Delta Put pos: %u, Get pos: %u.... ", putp - scast<sint8*>(dest), getp - scast<sint8*>(source)); 596 Byte cmd = xorSource[xorStart++]; 597 UInt16 count = cmd; 598 Boolean xorval = false; 599 600 if ((cmd & 0x80) == 0) 601 { 602 //0b00000000 603 if (cmd == 0) 604 { 605 if (xorStart >= xorEnd) 606 return; 607 count = (UInt16)(xorSource[xorStart++] & 0xFF); 608 if (xorStart >= xorEnd) 609 return; 610 value = xorSource[xorStart++]; 611 xorval = true; 612 //DEBUG_SAY("0b00000000 Val Count %d ", count); 613 //0b0??????? 614 } 615 } 616 else 617 { 618 //0b1??????? remove most significant bit 619 count &= 0x7F; 620 if (count != 0) 621 { 622 putp += count; 623 //DEBUG_SAY("0b1??????? Skip Count %d\n", count); 624 continue; 625 } 626 if (xorStart >= xorEnd) 627 return; 628 count = (UInt16) (xorSource[xorStart++] & 0xFF); 629 if (xorStart >= xorEnd) 630 return; 631 count += (UInt16) (xorSource[xorStart++] << 8); 632 633 //0b10000000 0 0 634 if (count == 0) 635 { 636 //DEBUG_SAY("0b10000000 Count %d to end delta\n", count); 637 return; 638 } 639 640 //0b100000000 0? 641 if ((count & 0x8000) == 0) 642 { 643 putp += count; 644 //DEBUG_SAY("0b100000000 0? Skip Count %d\n", count); 645 continue; 646 } 647 //0b10000000 11 648 if ((count & 0x4000) != 0) 649 { 650 count &= 0x3FFF; 651 if (xorStart >= xorEnd) 652 return; 653 value = xorSource[xorStart++]; 654 //DEBUG_SAY("0b10000000 11 Val Count %d ", count); 655 xorval = true; 656 //0b10000000 10 657 } 658 else 659 { 660 count &= 0x3FFF; 661 //DEBUG_SAY("0b10000000 10 XOR Count %d ", count); 662 } 663 } 664 665 if (xorval) 666 { 667 //DEBUG_SAY("XOR Val %d\n", value); 668 for (; count > 0; --count) 669 { 670 if (putp >= dataEnd) 671 return; 672 data[putp++] ^= value; 673 } 674 } 675 else 676 { 677 //DEBUG_SAY("XOR Source to Dest\n"); 678 for (; count > 0; --count) 679 { 680 if (putp >= dataEnd || xorStart >= xorEnd) 681 return; 682 data[putp++] ^= xorSource[xorStart++]; 683 } 684 } 685 } 686 } 687 688 } 689 }