Skip to content

Commit 79b991c

Browse files
committed
SPRTYL2PNG version 1.1
It is now possible to extract all graphics to a folder with a single button. Tile groups can be viewed.
1 parent aead385 commit 79b991c

File tree

14 files changed

+465
-183
lines changed

14 files changed

+465
-183
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ The first and only 1998 Gangsters Organized Crime MOD
1717
## How to install?
1818
1. First you MUST HAVE an original game, which can be obtained at GOG.com.
1919
2. Copy all the contents to install folder.
20-
3. If you have a non-GOG version, do not copy the `gangster.exe` or `gangsters-cheats.exe` executable.
20+
3. If you have a non-GOG version, do not copy the `gangster.exe` or `gangster-cheats.exe` executable.
2121

22-
## Cheats executable `gangsters-cheats.exe`
23-
It is used to facilitate testing and debugging. For now, it has the following cheats:
22+
## Cheats executable `gangster-cheats.exe`
23+
Created to facilitate debug or testing, it has the following cheats:
2424
* `Shift + $` Your balance becomes one million dollars.
2525
* `Shift + #` All gangs get 1000 dollars, including you.
26+
2627
## Graphics Fix
27-
The vast majority of graphics issues can be solved with [DxWrapper](https://github.com/elishacloud/dxwrapper) credits to [elishacloud](https://github.com/elishacloud) and [narzoul](https://github.com/narzoul).
28+
To run the game on Windows 10, the vast majority of graphics issues can be solved with [DxWrapper](https://github.com/elishacloud/dxwrapper) credits to [elishacloud](https://github.com/elishacloud) and [narzoul](https://github.com/narzoul).
2829
**You can get an already set up version here: [Tools/Misc](Tools/Misc/).** It's configured and ready to use, just extract the content in the game's installation folder.
2930

3031
For debugging it is highly recommended to run the game in **windowed mode**: Change the configuration file `dxwrapper.ini` to the following:
@@ -33,4 +34,4 @@ For debugging it is highly recommended to run the game in **windowed mode**: Cha
3334
Dd7to9 = 1
3435
[d3d9]
3536
EnableWindowMode = 1
36-
```
37+
```
-61 KB
Binary file not shown.
-4.67 MB
Binary file not shown.
-5 KB
Binary file not shown.

Tools/SPRTYL2PNG/Graphics.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using System.Windows.Media.Imaging;
8+
using System.Windows.Media;
9+
10+
namespace SPRTYL2PNG
11+
{
12+
public abstract class Graphics
13+
{
14+
public abstract int Quantity { get; }
15+
public byte[] RawData { get; }
16+
17+
protected Graphics(byte[] rawData)
18+
{
19+
RawData = rawData;
20+
}
21+
22+
internal abstract int WidthByIndex(int index);
23+
internal abstract int HeightByIndex(int index);
24+
internal abstract int OffsetByIndex(int index);
25+
internal abstract uint GetUnknowByIndex(int index);
26+
27+
internal int ByteSizeByIndex(int index)
28+
{
29+
return WidthByIndex(index) * HeightByIndex(index);
30+
}
31+
32+
public BitmapSource ExtractBitmap(int index, List<Color> palette, Color transparency)
33+
{
34+
var segment = new ArraySegment<byte>(RawData, OffsetByIndex(index), ByteSizeByIndex(index));
35+
return Make8bppBitmap(segment.ToArray(), WidthByIndex(index), HeightByIndex(index), palette, transparency);
36+
}
37+
38+
public void OverwriteBitmap(int index, BitmapSource bitmap, IList<Color> palette)
39+
{
40+
if (bitmap.Format != PixelFormats.Indexed8)
41+
throw new NotImplementedException("No palette! PNG format must be indexed with 256 colors (8-bits)");
42+
43+
//Check if palette match
44+
bool match = bitmap.Palette.Colors.Count == palette.Count;
45+
for (int i = 1; i < palette.Count && match; i++) //Skip firt color
46+
if (bitmap.Palette.Colors[i] != palette[i])
47+
match = false;
48+
49+
if (!match)
50+
throw new InvalidDataException("Color palette does not match");
51+
52+
if (bitmap.PixelWidth != WidthByIndex(index)
53+
|| bitmap.PixelHeight != HeightByIndex(index))
54+
throw new ArgumentException("Size mismatch");
55+
56+
int bSize = ByteSizeByIndex(index);
57+
int pos = OffsetByIndex(index);
58+
byte[] pixels = new byte[bSize];
59+
bitmap.CopyPixels(pixels, bitmap.PixelWidth, 0);
60+
61+
for (int i = 0; i < bSize; i++)
62+
RawData[pos + i] = pixels[i];
63+
}
64+
65+
private static BitmapSource Make8bppBitmap(byte[] rawImage, int width, int height, List<Color> palette, Color transparency)
66+
{
67+
int rawStride = (width * PixelFormats.Indexed8.BitsPerPixel + 7) / 8;
68+
if (rawImage.Length != rawStride * height)
69+
throw new ArgumentException("Size mismatch");
70+
palette[0] = transparency;
71+
72+
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Indexed8, new BitmapPalette(palette), rawImage, rawStride);
73+
}
74+
}
75+
}

Tools/SPRTYL2PNG/MainWindow.xaml

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
<DockPanel LastChildFill="True">
1010
<ToolBarTray DockPanel.Dock="Top">
1111
<ToolBar Band="1" >
12-
<Button Content="Open" Click="Open" Name="openButton">
13-
</Button>
14-
<Button Content="Save As..." IsEnabled="False" Name="saveAsButton" Click="SaveAs">
15-
</Button>
12+
<Button Content="Open" Click="Open" Name="openButton" />
13+
<Button Content="Save As..." IsEnabled="False" Name="saveAsButton" Click="SaveAs" />
14+
<Separator/>
15+
<Button Content="Export All as PNG..." IsEnabled="False" Click="ExportAll" Name="exportButton" />
1616
</ToolBar>
1717
<ToolBar Band="2" Name="previewOptionsMenu" IsEnabled="False">
1818
<Label>Palette:</Label>
@@ -61,15 +61,30 @@
6161
</ToolBar>
6262
</ToolBarTray>
6363
<StatusBar Name="bottomBar" DockPanel.Dock="Bottom" IsEnabled="False">
64-
<Label>Selected Frame:</Label>
65-
<Image Stretch="None" Name="selectedFrame" />
66-
<Label Name="selectedFrameSize" ContentStringFormat="Size: {0}px"/>
67-
<Label Name="selectedFrameIndex" ContentStringFormat="Index: {0}" />
68-
<Button Click="ExportPNGFrame">Export Frame as PNG</Button>
69-
<Button Click="ImportPNGFrame">Replace Frame with PNG</Button>
64+
<Label>Selected:</Label>
65+
<Image Stretch="None" Name="selectedTile" />
66+
<StackPanel>
67+
<Label Name="selectedSpriteSize" ContentStringFormat="Size: {0} px" Padding="5,5,5,0"/>
68+
<Label Name="selectedSpriteIndex" ContentStringFormat="Index: {0}" Padding="5,0,5,0" />
69+
<Label Name="selectedSpriteUnknow" ContentStringFormat="Unknow: {0:x8}" Padding="5,0,5,5" />
70+
</StackPanel>
71+
<Button Click="ExportPNGSprite">Export as PNG</Button>
72+
<Button Name="replaceButton" Click="ImportPNGSprite">Replace with PNG</Button>
7073
</StatusBar>
71-
<ScrollViewer VerticalScrollBarVisibility="Visible">
72-
<WrapPanel Name="previewPanel"/>
73-
</ScrollViewer>
74+
<Grid>
75+
<Grid.ColumnDefinitions>
76+
<ColumnDefinition Width="*"></ColumnDefinition>
77+
<ColumnDefinition Width="0" Name="tileColumnDef"></ColumnDefinition>
78+
</Grid.ColumnDefinitions>
79+
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Visible">
80+
<WrapPanel Name="previewPanel"/>
81+
</ScrollViewer>
82+
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Visible">
83+
<WrapPanel Name="previewTilePanel"/>
84+
</ScrollViewer>
85+
86+
87+
</Grid>
88+
7489
</DockPanel>
7590
</Window>

Tools/SPRTYL2PNG/MainWindow.xaml.cs

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Windows.Media;
66
using System.Windows.Media.Imaging;
77
using System.Windows.Shapes;
8+
using Path = System.IO.Path;
89

910
namespace SPRTYL2PNG
1011
{
@@ -14,7 +15,7 @@ namespace SPRTYL2PNG
1415
public partial class MainWindow : Window
1516
{
1617
string lastOpenedFile;
17-
Sprite loadedSprite;
18+
Graphics loadedGraphics;
1819
Color[] transparencyColor = new Color[]{ Colors.Magenta, Colors.Black, Colors.White };
1920

2021
public MainWindow()
@@ -25,12 +26,13 @@ public MainWindow()
2526
private void UpdatePreviewPanel()
2627
{
2728
previewPanel.Children.Clear();
29+
previewTilePanel.Children.Clear();
2830

2931
if (zoomCombo.SelectedIndex > 0)
3032
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor);
3133
else RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.Fant);
3234

33-
var spriteImage = new Image[loadedSprite.Count];
35+
var spriteImage = new Image[loadedGraphics.Quantity];
3436
for (int i = 0; i < spriteImage.Length; i++)
3537
{
3638
spriteImage[i] = new Image();
@@ -41,17 +43,48 @@ private void UpdatePreviewPanel()
4143
spriteImage[i].Tag = i;
4244
spriteImage[i].MouseDown += (sender, args) =>
4345
{
44-
selectedFrame.Source = ((Image)sender).Source;
45-
selectedFrameIndex.Content = ((Image)sender).Tag;
46-
selectedFrameSize.Content = selectedFrame.Source.Width.ToString() + ";" + selectedFrame.Source.Height.ToString();
46+
selectedTile.Source = ((Image)sender).Source;
47+
selectedSpriteIndex.Content = ((Image)sender).Tag;
48+
selectedSpriteSize.Content = selectedTile.Source.Width.ToString() + ", " + selectedTile.Source.Height.ToString();
49+
selectedSpriteUnknow.Content = loadedGraphics.GetUnknowByIndex((int)((Image)sender).Tag);
4750
bottomBar.IsEnabled = true;
51+
replaceButton.IsEnabled = true;
4852
};
4953
spriteImage[i].SnapsToDevicePixels = true;
5054
spriteImage[i].LayoutTransform = new ScaleTransform(zoomCombo.SelectedIndex + 1, zoomCombo.SelectedIndex + 1);
51-
spriteImage[i].Source = loadedSprite.ToBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
55+
spriteImage[i].Source = loadedGraphics.ExtractBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
5256
previewPanel.Children.Add(spriteImage[i]);
5357
}
54-
58+
59+
var tg = (TileSet)loadedGraphics;
60+
if (tg.TileSetGroupInformation != null)
61+
{
62+
tileColumnDef.Width = new GridLength(250);
63+
var spriteTileImage = new Image[tg.TileSetGroupInformation.Length];
64+
for (int i = 0; i < spriteTileImage.Length; i++)
65+
{
66+
spriteTileImage[i] = new Image();
67+
68+
spriteTileImage[i].Margin = new Thickness(marginsStroke.SelectedIndex * 4);
69+
70+
spriteTileImage[i].Stretch = Stretch.None;
71+
spriteTileImage[i].Tag = i;
72+
spriteTileImage[i].MouseDown += (sender, args) =>
73+
{
74+
selectedTile.Source = ((Image)sender).Source;
75+
selectedSpriteIndex.Content = ((Image)sender).Tag;
76+
selectedSpriteSize.Content = selectedTile.Source.Width.ToString() + ", " + selectedTile.Source.Height.ToString();
77+
selectedSpriteUnknow.Content = tg.GetUnknowByIndex((int)((Image)sender).Tag);
78+
bottomBar.IsEnabled = true;
79+
replaceButton.IsEnabled = false;
80+
};
81+
spriteTileImage[i].SnapsToDevicePixels = true;
82+
spriteTileImage[i].LayoutTransform = new ScaleTransform(zoomCombo.SelectedIndex + 1, zoomCombo.SelectedIndex + 1);
83+
spriteTileImage[i].Source = tg.ExtractTileGroupBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
84+
previewTilePanel.Children.Add(spriteTileImage[i]);
85+
}
86+
}
87+
else tileColumnDef.Width = new GridLength(0);
5588
}
5689

5790
private void Open(object sender, RoutedEventArgs e)
@@ -60,12 +93,64 @@ private void Open(object sender, RoutedEventArgs e)
6093
openFileDialog.Filter = "Sprite or Tileset Files|*.spr;*.tyl|Sprite Files|*.spr|Tileset Files|*.tyl";
6194
if (openFileDialog.ShowDialog() == true)
6295
{
96+
var fileName = Path.GetFileName(openFileDialog.FileName.ToLower());
6397
bottomBar.IsEnabled = false;
6498
previewOptionsMenu.IsEnabled = true;
65-
lastOpenedFile = openFileDialog.FileName;
99+
exportButton.IsEnabled = true;
100+
lastOpenedFile = openFileDialog.FileName.ToLower();
66101
this.Title = openFileDialog.FileName + " - SPRTYL2PNG";
67-
bool header = System.IO.Path.GetExtension(openFileDialog.FileName).ToUpper() == ".SPR";
68-
loadedSprite = new Sprite(File.OpenRead(openFileDialog.FileName), header);
102+
103+
if(fileName.EndsWith(".spr"))
104+
loadedGraphics = new SpriteSheet(File.ReadAllBytes(openFileDialog.FileName));
105+
106+
else if (fileName.EndsWith(".tyl"))
107+
{
108+
int size = 32;
109+
bool hasHeader = false , hasUnknowTail = false;
110+
111+
switch (fileName)
112+
{
113+
case "medborders.tyl":
114+
size = 20; hasHeader = true;
115+
break;
116+
case "mediumroofs.tyl":
117+
size = 20;
118+
break;
119+
case "lowborders.tyl":
120+
hasHeader = true;
121+
break;
122+
case "locations.tyl":
123+
hasUnknowTail = true;
124+
break;
125+
}
126+
127+
loadedGraphics = new TileSet(hasHeader, hasUnknowTail, size, File.ReadAllBytes(openFileDialog.FileName));
128+
129+
if(fileName == "locations.tyl") // If true, load locations.dat
130+
{
131+
string path = openFileDialog.FileName.ToLower().Replace(".tyl", ".dat");
132+
using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
133+
{
134+
br.BaseStream.Position = 60;
135+
int groupCount = (int)br.ReadInt16() * 4;
136+
TileSetGroupInformation[] tsgi = new TileSetGroupInformation[groupCount];
137+
138+
for (int i = 0; i < groupCount; i++)
139+
{
140+
if (i != 0 && (i & 3) == 0) //Mulltiple of 4, not zero
141+
br.BaseStream.Position++;
142+
143+
tsgi[i] = new TileSetGroupInformation(br);
144+
}
145+
146+
((TileSet)loadedGraphics).TileSetGroupInformation = tsgi;
147+
}
148+
149+
150+
}
151+
152+
}
153+
69154
UpdatePreviewPanel();
70155
}
71156
}
@@ -76,9 +161,10 @@ private void SaveAs(object sender, RoutedEventArgs e)
76161
saveFileDialog.FileName = lastOpenedFile;
77162
saveFileDialog.Filter = "Sprite Files|*.spr|Tileset Files|*.tyl";
78163
saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(lastOpenedFile);
164+
if (lastOpenedFile.EndsWith(".tyl")) saveFileDialog.FilterIndex = 1;
79165
if (saveFileDialog.ShowDialog() == true)
80166
{
81-
File.WriteAllBytes(saveFileDialog.FileName, loadedSprite.RawData);
167+
File.WriteAllBytes(saveFileDialog.FileName, loadedGraphics.RawData);
82168
this.Title = this.Title.Remove(this.Title.Length - 1);
83169
}
84170
}
@@ -88,24 +174,26 @@ private void previewOptionsChanged(object sender, SelectionChangedEventArgs e)
88174
UpdatePreviewPanel();
89175
}
90176

91-
private void ExportPNGFrame(object sender, RoutedEventArgs e)
177+
private void ExportPNGSprite(object sender, RoutedEventArgs e)
92178
{
93179
Microsoft.Win32.SaveFileDialog exportFileDialog = new Microsoft.Win32.SaveFileDialog();
94180
exportFileDialog.Filter = "PNG-8|*.png";
95181
if (exportFileDialog.ShowDialog() == true)
96182
{
97183
//Create a 8bit PNG with transparency
98-
var bitmap = loadedSprite.ToBitmap(int.Parse(selectedFrameIndex.Content.ToString()), GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0,0,0,0));
184+
185+
//var bitmap = loadedGraphics.ExtractBitmap(int.Parse(selectedSpriteIndex.Content.ToString()), GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0,0,0,0));
186+
var bitmap = selectedTile.Source;
99187
using (var fileStream = new FileStream(exportFileDialog.FileName, FileMode.Create))
100188
{
101189
BitmapEncoder encoder = new PngBitmapEncoder();
102-
encoder.Frames.Add(BitmapFrame.Create(bitmap));
190+
encoder.Frames.Add(BitmapFrame.Create(bitmap as BitmapSource));
103191
encoder.Save(fileStream);
104192
}
105193
}
106194
}
107195

108-
private void ImportPNGFrame(object sender, RoutedEventArgs e)
196+
private void ImportPNGSprite(object sender, RoutedEventArgs e)
109197
{
110198
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
111199
openFileDialog.Filter = "PNG-8|*.png";
@@ -119,7 +207,7 @@ private void ImportPNGFrame(object sender, RoutedEventArgs e)
119207

120208
try
121209
{
122-
loadedSprite.Replace(int.Parse(selectedFrameIndex.Content.ToString()), bs, GamePalettes.ByIndex(palettePicker.SelectedIndex));
210+
loadedGraphics.OverwriteBitmap(int.Parse(selectedSpriteIndex.Content.ToString()), bs, GamePalettes.ByIndex(palettePicker.SelectedIndex));
123211
}
124212
catch (NotImplementedException)
125213
{
@@ -148,5 +236,29 @@ private void ImportPNGFrame(object sender, RoutedEventArgs e)
148236
}
149237

150238
}
239+
240+
private void ExportAll(object sender, RoutedEventArgs e)
241+
{
242+
Microsoft.Win32.SaveFileDialog exportFileDialog = new Microsoft.Win32.SaveFileDialog();
243+
exportFileDialog.Filter = "PNG-8|*.png";
244+
exportFileDialog.Title = "All frames will be exported";
245+
exportFileDialog.FileName = "X.png";
246+
if (exportFileDialog.ShowDialog() == true)
247+
{
248+
for (int i = 0; i < loadedGraphics.Quantity; i++)
249+
{
250+
string exportName = System.IO.Path.GetDirectoryName(exportFileDialog.FileName) + "\\" + i.ToString() + ".PNG";
251+
//Create a 8bit PNG with transparency
252+
var bitmap = loadedGraphics.ExtractBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0, 0, 0, 0));
253+
using (var fileStream = new FileStream(exportName, FileMode.Create))
254+
{
255+
BitmapEncoder encoder = new PngBitmapEncoder();
256+
encoder.Frames.Add(BitmapFrame.Create(bitmap));
257+
encoder.Save(fileStream);
258+
}
259+
260+
}
261+
}
262+
}
151263
}
152264
}

0 commit comments

Comments
 (0)