/// <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 static City ParseCityFile(string binaryFilename) { City city; using (FileStream reader = File.Open(binaryFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) { city = new City(ParseHeader(reader)); Segments.DataSegment segment; // Begin walking through the file, handing off segment parsing to the appropriate parser. while (reader.Position < reader.Length) { segment = Segments.SegmentFactory.ParseSegment(reader); segment.PopulateCity(ref city); city.Segments.Add(segment); /* 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 { throw new Exception("Reached end of parse loop, unknown data block case should have been handled.") }*/ } } return city; }
public void CMTInit() { testCity = CityParser.ParseCityFile("blank.sc2"); Trace.WriteLine("City " + testCity.CityName + " loaded."); }
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 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 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 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 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 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 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 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; }
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; }
/// <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; }
public void Initialize() { city = CityParser.ParseCityFile("blank.sc2"); Trace.WriteLine("Data length came out to be " + city.DataLength + " bytes."); }
private static void storeCity(City parserCity, string username, string filepath, Stream cityFileStream) { // Fetch relevant data from parserCity. var city = new CityInfo(parserCity, username, filepath, DateTime.Now); // Save .sc2 file on the server. cityFileStream.Position = 0; using (Stream outputStream = File.OpenWrite(filepath)) { cityFileStream.CopyTo(outputStream); } // Save parsed city data to database. using (var db = new DatabaseContext()) { db.CityInfoes.Add(city); db.SaveChanges(); } }