-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCityParser.cs
More file actions
741 lines (648 loc) · 29.8 KB
/
CityParser.cs
File metadata and controls
741 lines (648 loc) · 29.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace CityParser2000
{
public class CityParser
{
#region local constants
// Binary segments describing city maps which are solely integer values.
private static readonly HashSet<string> integerMaps = new HashSet<string> { "XPLC", "XFIR", "XPOP", "XROG", "XTRF", "XPLT", "XVAL", "XCRM" };
// Binary segments describing city maps in which the byte data is uniqure to each segment.
private static readonly HashSet<string> complexMaps = new HashSet<string> { "XTER", "XBLD", "XZON", "XUND", "XTXT", "XBIT", "ALTM" };
// Binary codes that indicate what is underground in a tile. Multiples distinguish slope and direction.
// Used when decoding XUND segment.
private enum undergroundCode {
nothing = 0x00,
subway1 = 0x01,
subway2 = 0x02,
subway3 = 0x03,
subway4 = 0x04,
subway5 = 0x05,
subway6 = 0x06,
subway7 = 0x07,
subway8 = 0x08,
subway9 = 0x09,
subwayA = 0x0A,
subwayB = 0x0B,
subwayC = 0x0C,
subwayD = 0x0D,
subwayE = 0x0E,
subwayF = 0x0F,
pipe1 = 0x10,
pipe2 = 0x11,
pipe3 = 0x12,
pipe4 = 0x13,
pipe5 = 0x14,
pipe6 = 0x15,
pipe7 = 0x16,
pipe8 = 0x17,
pipe9 = 0x18,
pipeA = 0x19,
pipeB = 0x1A,
pipeC = 0x1B,
pipeD = 0x1C,
pipeE = 0x1D,
pipeF = 0x1E,
pipeAndSubway1 = 0x1F,
pipeAndSubway2 = 0x20,
tunnel1 = 0x21,
tunnel2 = 0x22,
subwayStationOrSubRail = 0x23
};
// Zones. Order is important as this is used in decoding binary data.
private enum zoneCode { none, lightResidential, denseResidential, lightCommercial, denseCommercial, lightIndustrial, denseIndustrial, military, airport, seaport };
#endregion
#region constructors
static void Main()
{
CityParser parser = new CityParser();
City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\dustropolis.sc2");
//City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\new city.sc2");
//City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\dustropolis.sc2");
//City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\altTest2.sc2");
//City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\zoneTest.sc2");
//City ourCity = parser.ParseBinaryFile("C:\\Users\\Owner\\Desktop\\CitiesSC2000\\underground_test.sc2");
}
/// <summary>
/// The <c>CityParser</c> type converts binary Sim City 2000 files to <see cref="City"/> <c>Objects</c>.
/// </summary>
public CityParser ()
{
tileIterator = new Utility.CityTileIterator(City.TilesPerSide);
}
#endregion
#region local variables
private Utility.CityTileIterator tileIterator;
#endregion
#region parsing and storage
/// <summary>
/// Parses binary data from <paramref name="binaryFilename"/> and stores it in a <see cref="City"/> object.
/// </summary>
/// <param name="binaryFilename">Filepath to a .SC2 file.</param>
/// <returns>A <see cref="City"/> instance reflecting data from <paramref name="binaryFilename"/></returns>
public City ParseBinaryFile(string binaryFilename)
{
var city = new City();
using (BinaryReader reader = new BinaryReader(File.Open(binaryFilename, FileMode.Open)))
{
// Read 12-byte header.
string iffType = readString(reader, 4);
reader.ReadBytes(4);
var fileType = readString(reader, 4);
if (!iffType.Equals("FORM") || !fileType.Equals("SCDH"))
{
// This is not a Sim City 2000 file.
throw new System.InvalidOperationException("Invalid input: Not a SC2000 file.");
}
// The rest of the file is divided into segments.
// Each segment begins with a 4-byte segment name, followed by a 32-bit integer segment length.
// Most segments are compressed using a simple run-length compression scheme, and must be
// decompressed before they can be parsed correctly.
string segmentName;
Int32 segmentLength;
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
// Parse segment data and store it in a City object.
segmentName = readString(reader, 4);
segmentLength = readInt32(reader);
if ("CNAM".Equals(segmentName))
{
// City name (uncompressed).
city = parseCityName(city, reader, segmentLength);
}
else if ("MISC".Equals(segmentName))
{
// MISC contains a series of 32-bit integers.
city = parseMiscValues(city, getDecompressedReader(reader, segmentLength));
}
else if ("ALTM".Equals(segmentName))
{
// Altitude map. (Not compressed)
city = parseAltitudeMap(city, reader, segmentLength);
}
else if ("XTER".Equals(segmentName))
{
// Terrain slope map.
// Ignore for now.
reader.ReadBytes(segmentLength);
}
else if ("XBLD".Equals(segmentName))
{
// Buildings map.
city = parseBuildingMap(city, getDecompressedReader(reader, segmentLength));
}
else if ("XZON".Equals(segmentName))
{
// Zoning map (also specifies building corners).
city = parseZoningMap(city, getDecompressedReader(reader, segmentLength));
}
else if ("XUND".Equals(segmentName))
{
// Underground structures map.
city = parseUndergroundMap(city, getDecompressedReader(reader, segmentLength));
}
else if ("XTXT".Equals(segmentName))
{
// Sign information, of some sort.
// Ignore for now.
reader.ReadBytes(segmentLength);
}
else if ("XLAB".Equals(segmentName))
{
// 256 Labels. Mayor's name, then sign text.
city = parse256Labels(city, getDecompressedReader(reader, segmentLength));
}
else if ("XMIC".Equals(segmentName))
{
// Microcontroller info.
// Ignore for now.
reader.ReadBytes(segmentLength);
}
else if ("XTHG".Equals(segmentName))
{
// Segment contents unknown.
// Ignore for now.
reader.ReadBytes(segmentLength);
}
else if ("XBIT".Equals(segmentName))
{
// One byte of flags for each city tile.
city = parseBinaryFlagMap(city, getDecompressedReader(reader, segmentLength));
}
else if (integerMaps.Contains(segmentName))
{
// Data in these segments are represented by integer values ONLY.
city = parseIntegerMap(city, segmentName, getDecompressedReader(reader, segmentLength));
}
else
{
// Unknown segment, ignore.
reader.ReadBytes(segmentLength);
}
}
}
return city;
}
#region complex city map parsers
private City parseBinaryFlagMap(City city, BinaryReader segmentReader)
{
// Parse XBIT segment.
// XBIT contains one byte of binary flags for each city tile.
//
// The flags for each bit are:
// 0: Salt water. (If true and this tile has water it will be salt water)
// 1: (unknown)
// 2: Water covered.
// 3: (unknown)
// 4: Supplied with water from city water-system.
// 5: Conveys water-system water. (Building and pipes convey water)
// 6: Has electricty.
// 7: Conducts electricity.
bool saltyFlag;
bool waterCoveredFlag;
bool waterSuppliedFlag;
bool pipedFlag;
bool poweredFlag;
bool conductiveFlag;
// These will be used to set the bool flags.
const byte saltyMask = 1;
// Unknown flag in 1 << 1 position.
const byte waterCoveredMask = 1 << 2;
// Unknown flag in 1 << 3 position.
const byte waterSuppliedMask = 1 << 4;
const byte pipedMask = 1 << 5;
const byte poweredMask = 1 << 6;
const byte conductiveMask = 1 << 7;
byte tileByte;
tileIterator.Reset();
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
// TODO: Possible bug. Test data "new city.sc2" does not seem to be decompressing this segment correctly.
tileByte = segmentReader.ReadByte();
saltyFlag = (tileByte & saltyMask) != 0;
waterCoveredFlag = (tileByte & waterCoveredMask) != 0;
waterSuppliedFlag = (tileByte & waterSuppliedMask) != 0;
pipedFlag = (tileByte & pipedMask) != 0;
poweredFlag = (tileByte & poweredMask) != 0;
conductiveFlag = (tileByte & conductiveMask) != 0;
city.SetTileFlags(tileIterator.X, tileIterator.Y, saltyFlag, waterCoveredFlag, waterSuppliedFlag, pipedFlag, poweredFlag, conductiveFlag);
tileIterator.IncrementCurrentTile();
}
segmentReader.Dispose();
return city;
}
private City parseUndergroundMap(City city, BinaryReader segmentReader)
{
// Parse XUND segment.
// This segment indicates what exists underground in each tile, given by a one-byte integer code.
undergroundCode tileCode;
tileIterator.Reset();
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
tileCode = (undergroundCode) segmentReader.ReadByte();
switch (tileCode)
{
case undergroundCode.nothing:
// This tile doesn't have anything under the ground.
break;
case undergroundCode.pipeAndSubway1:
case undergroundCode.pipeAndSubway2:
city.SetUndergroundItem(tileIterator.X, tileIterator.Y, City.UndergroundItem.SubwayAndPipe);
break;
case undergroundCode.subwayStationOrSubRail:
city.SetUndergroundItem(tileIterator.X, tileIterator.Y, City.UndergroundItem.SubwayStation);
break;
case undergroundCode.tunnel1:
case undergroundCode.tunnel2:
// NOTE: These codes appear to have not been used... nor does there appear to be any underground code at all for tunnels.
// Perhaps these codes were meant to be tunnels but were never implemented as such, or possibly these codes indicate some other non-tunnel underground object.
// TODO: Log if we ever get here?
city.SetUndergroundItem(tileIterator.X, tileIterator.Y, City.UndergroundItem.Tunnel);
break;
case undergroundCode.subway1:
case undergroundCode.subway2:
case undergroundCode.subway3:
case undergroundCode.subway4:
case undergroundCode.subway5:
case undergroundCode.subway6:
case undergroundCode.subway7:
case undergroundCode.subway8:
case undergroundCode.subway9:
case undergroundCode.subwayA:
case undergroundCode.subwayB:
case undergroundCode.subwayC:
case undergroundCode.subwayD:
case undergroundCode.subwayE:
case undergroundCode.subwayF:
city.SetUndergroundItem(tileIterator.X, tileIterator.Y, City.UndergroundItem.Subway);
break;
case undergroundCode.pipe1:
case undergroundCode.pipe2:
case undergroundCode.pipe3:
case undergroundCode.pipe4:
case undergroundCode.pipe5:
case undergroundCode.pipe6:
case undergroundCode.pipe7:
case undergroundCode.pipe8:
case undergroundCode.pipe9:
case undergroundCode.pipeA:
case undergroundCode.pipeB:
case undergroundCode.pipeC:
case undergroundCode.pipeD:
case undergroundCode.pipeE:
case undergroundCode.pipeF:
city.SetUndergroundItem(tileIterator.X, tileIterator.Y, City.UndergroundItem.Pipe);
break;
default:
// Note: Hex codes over 0x23 are likely unused, but if they are used we would end up here.
break;
}
tileIterator.IncrementCurrentTile();
}
segmentReader.Dispose();
return city;
}
private City parseZoningMap(City city, BinaryReader segmentReader)
{
// Parse zoning and "building corner" information.
// b00001111. The zone information is encoded in bits 0-3
byte zoneMask = 15;
// b0001000. Set if building has a corner in the 'top right'.
byte cornerMask1 = 16;
// b00100000. Set if building has a corner in the 'bottom right'.
byte cornerMask2 = 32;
// b01000000. Set if building has a corner in the 'bottom left'.
byte cornerMask3 = 64;
// b10000000. Set if building has a corner in the 'top left'.
byte cornerMask4 = 128;
zoneCode tileZoneCode;
tileIterator.Reset();
byte rawByte;
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
rawByte = segmentReader.ReadByte();
// A little bit-wise arithmetic to extract our 4-bit zone code.
tileZoneCode = (zoneCode) (rawByte & zoneMask);
switch (tileZoneCode)
{
case zoneCode.lightResidential:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.LightResidential);
break;
case zoneCode.denseResidential:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.DenseResidential);
break;
case zoneCode.lightCommercial:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.LightCommercial);
break;
case zoneCode.denseCommercial:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.DenseCommercial);
break;
case zoneCode.lightIndustrial:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.LightIndustrial);
break;
case zoneCode.denseIndustrial:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.DenseIndustrial);
break;
case zoneCode.military:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.MilitaryBase);
break;
case zoneCode.airport:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.Airport);
break;
case zoneCode.seaport:
city.SetZone(tileIterator.X, tileIterator.Y, City.Zone.Seaport);
break;
}
if (hasCorner(rawByte, cornerMask1))
{
city.SetBuildingCorner(tileIterator.X, tileIterator.Y, Building.CornerCode.TopRight);
}
if (hasCorner(rawByte, cornerMask2))
{
city.SetBuildingCorner(tileIterator.X, tileIterator.Y, Building.CornerCode.BottomRight);
}
if (hasCorner(rawByte, cornerMask3))
{
city.SetBuildingCorner(tileIterator.X, tileIterator.Y, Building.CornerCode.BottomLeft);
}
if (hasCorner(rawByte, cornerMask4))
{
city.SetBuildingCorner(tileIterator.X, tileIterator.Y, Building.CornerCode.TopLeft);
}
tileIterator.IncrementCurrentTile();
}
segmentReader.Dispose();
return city;
}
private bool hasCorner(byte b, byte cornerMask)
{
return (b & cornerMask) == (byte) 1;
}
private City parseBuildingMap(City city, BinaryReader segmentReader)
{
// This segment indicates what is above ground in each square.
// TODO: Shouldn't be relying on "Building.BuildingCode" order like this. BAD.
tileIterator.Reset();
byte rawByte;
Building.BuildingCode buildingCode;
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
// This map contains on 'building code' for each square.
// The building code is a one-byte integer value.
rawByte = segmentReader.ReadByte();
buildingCode = (Building.BuildingCode) rawByte;
city.SetBuilding(tileIterator.X, tileIterator.Y, buildingCode);
tileIterator.IncrementCurrentTile();
}
segmentReader.Dispose();
return city;
}
private City parseAltitudeMap(City city, BinaryReader reader, int segmentLength)
{
// Altitude map.
// This segment is NOT compressed.
// Each square gets two bytes.
byte byteOne;
byte byteTwo;
int altitude;
// b00011111. Altitude is stored in bits 0-4.
byte altitudeMask = 31;
tileIterator.Reset();
long readerStopPosition = reader.BaseStream.Position + segmentLength;
while (reader.BaseStream.Position < readerStopPosition)
{
// Don't do anything with the first byte (at least for now).
byteOne = reader.ReadByte();
byteTwo = reader.ReadByte();
// In SC2000 the minimum altitude is 50 and the maximum is 3150, thus the 50's below.
altitude = ((altitudeMask & byteTwo) * 50) + 50;
city.SetAltitude(tileIterator.X, tileIterator.Y, altitude);
tileIterator.IncrementCurrentTile();
}
return city;
}
#endregion
private City parseIntegerMap(City city, string segmentName, BinaryReader segmentReader)
{
List<int> mapData = new List<int>();
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
mapData.Add((int)segmentReader.ReadByte());
}
city = storeIntegerMapData(city, mapData, segmentName);
return city;
}
private City storeIntegerMapData(City city, List<int> mapData, string segmentName)
{
if ("XTRF".Equals(segmentName))
{
city.SetTrafficMap(mapData);
}
else if ("XPLT".Equals(segmentName))
{
city.SetPollutionMap(mapData);
}
else if ("XVAL".Equals(segmentName))
{
city.SetPropertyValueMap(mapData);
}
else if ("XCRM".Equals(segmentName))
{
city.SetCrimeMap(mapData);
}
else if ("XPLC".Equals(segmentName))
{
city.SetPoliceMap(mapData);
}
else if ("XFIR".Equals(segmentName))
{
city.SetFirefighterMap(mapData);
}
else if ("XPOP".Equals(segmentName))
{
city.SetPopulationMap(mapData);
}
else if ("XROG".Equals(segmentName))
{
city.SetPopulationGrowthMap(mapData);
}
return city;
}
private City parseCityName(City city, BinaryReader reader, int segmentLength)
{
byte nameLength = reader.ReadByte();
string cityName = readString(reader, nameLength);
// Remove garbage characters that are at the end of the name.
int gibbrishStart = cityName.IndexOf("\0");
cityName = cityName.Remove(gibbrishStart);
// City name is possibly padded. Ignore this padding.
// NOTE: I yet to see a case where there actually is padding. I believe this is unrelated to the gibberish removal above, but I could be wrong.
if (nameLength < segmentLength - 1)
{
reader.ReadBytes(segmentLength - nameLength - 1);
}
city.CityName = cityName;
return city;
}
private City parseMiscValues(City city, BinaryReader segmentReader)
{
// The MISC segment contains ~1200 integer values.
// TODO: Still a lot of work to be done on this segment. Aka: we don't know what most of these numbers mean, and are just recording them.
Int32 miscValue;
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
miscValue = readInt32(segmentReader);
city.AddMiscValue(miscValue);
}
segmentReader.Dispose();
return city;
}
private City parse256Labels(City city, BinaryReader segmentReader)
{
// This segment describes 256 strings. String 0 is the mayor's name, the remaining are text from user-generated signs in the city.
int labelLength;
string label;
const int maxLabelLength = 24;
// Parse mayor's name.
labelLength = segmentReader.ReadByte();
label = readString(segmentReader, labelLength);
if (maxLabelLength - labelLength > 0)
{
segmentReader.ReadBytes(maxLabelLength - labelLength);
}
city.MayorName = label;
while (segmentReader.BaseStream.Position < segmentReader.BaseStream.Length)
{
// Parse sign-text strings.
// Each string is 24 bytes long, and is preceded by a 1-byte count.
labelLength = segmentReader.ReadByte();
label = readString(segmentReader, labelLength);
city.AddSignText(label);
// Advance past any padding to next label.
if (maxLabelLength - labelLength > 0)
{
segmentReader.ReadBytes(maxLabelLength - labelLength);
}
}
segmentReader.Dispose();
return city;
}
#endregion
#region utility functions
private MemoryStream decompressSegment(BinaryReader compressed, int compressedLength)
{
// Data is compressed using a simple run-length encoding.
var decompressed = new MemoryStream();
var writer = new BinaryWriter(decompressed);
int segmentPos = 0;
byte chunkCode;
byte repeatByte;
while (segmentPos < compressedLength)
{
chunkCode = compressed.ReadByte();
segmentPos++;
// The chunkCode value determines how it should be interpreted...
if (chunkCode < 127)
{
// Chunk code describes how many (already not compressed) bytes are in this chunk.
writer.Write(compressed.ReadBytes(chunkCode));
segmentPos += chunkCode;
}
else
{
// Chunk code describes how many times the following byte should be repeated.
// Subtract to obtain number of repetitions.
chunkCode -= 127;
repeatByte = compressed.ReadByte();
segmentPos++;
for (int i = 0; i < chunkCode; i++)
{
writer.Write(repeatByte);
}
}
}
decompressed.Position = 0;
return decompressed;
}
private BinaryReader getDecompressedReader(BinaryReader compressedReader, int compressedLength)
{
return new BinaryReader(decompressSegment(compressedReader, compressedLength));
}
private string readString(BinaryReader reader, int length)
{
byte[] buffer = reader.ReadBytes(length);
return Encoding.ASCII.GetString(buffer);
}
private Int16 readInt16(BinaryReader reader)
{
Int16 i = reader.ReadInt16();
return BitConverter.IsLittleEndian ? toLittleEndian(i) : i;
}
private Int32 readInt32(BinaryReader reader)
{
Int32 i = reader.ReadInt32();
return BitConverter.IsLittleEndian ? toLittleEndian(i) : i;
}
private Int32 toLittleEndian(Int32 bigEndian)
{
// IPAddress happens to have a function what does what we want.
// This obviously has nothing to do with networking.
return IPAddress.HostToNetworkOrder(bigEndian);
}
private Int16 toLittleEndian(Int16 bigEndian)
{
// IPAddress happens to have a function what does what we want.
// This obviously has nothing to do with networking.
return IPAddress.HostToNetworkOrder(bigEndian);
}
private byte toLittleEndian(byte bigEndian)
{
return ReverseWithLookupTable(bigEndian);
}
private static readonly byte[] BitReverseTable =
{
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};
private byte ReverseWithLookupTable(byte toReverse)
{
return BitReverseTable[toReverse];
}
#endregion
}
}