Skip to content

Commit

Permalink
SPRTYL2PNG version 1.1
Browse files Browse the repository at this point in the history
It is now possible to extract all graphics to a folder with a single button. Tile groups can be viewed.
  • Loading branch information
THGSCST committed Dec 23, 2022
1 parent aead385 commit 79b991c
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 183 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ The first and only 1998 Gangsters Organized Crime MOD
## How to install?
1. First you MUST HAVE an original game, which can be obtained at GOG.com.
2. Copy all the contents to install folder.
3. If you have a non-GOG version, do not copy the `gangster.exe` or `gangsters-cheats.exe` executable.
3. If you have a non-GOG version, do not copy the `gangster.exe` or `gangster-cheats.exe` executable.

## Cheats executable `gangsters-cheats.exe`
It is used to facilitate testing and debugging. For now, it has the following cheats:
## Cheats executable `gangster-cheats.exe`
Created to facilitate debug or testing, it has the following cheats:
* `Shift + $` Your balance becomes one million dollars.
* `Shift + #` All gangs get 1000 dollars, including you.

## Graphics Fix
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).
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).
**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.

For debugging it is highly recommended to run the game in **windowed mode**: Change the configuration file `dxwrapper.ini` to the following:
Expand All @@ -33,4 +34,4 @@ For debugging it is highly recommended to run the game in **windowed mode**: Cha
Dd7to9 = 1
[d3d9]
EnableWindowMode = 1
```
```
Binary file removed Tools/SPRTYL2PNG/.vs/SPR2BMP/v16/.suo
Binary file not shown.
Binary file removed Tools/SPRTYL2PNG/.vs/SPR2BMP/v16/Browse.VC.db
Binary file not shown.
Binary file removed Tools/SPRTYL2PNG/.vs/SPRTYL2PNG/v16/.suo
Binary file not shown.
75 changes: 75 additions & 0 deletions Tools/SPRTYL2PNG/Graphics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace SPRTYL2PNG
{
public abstract class Graphics
{
public abstract int Quantity { get; }
public byte[] RawData { get; }

protected Graphics(byte[] rawData)
{
RawData = rawData;
}

internal abstract int WidthByIndex(int index);
internal abstract int HeightByIndex(int index);
internal abstract int OffsetByIndex(int index);
internal abstract uint GetUnknowByIndex(int index);

internal int ByteSizeByIndex(int index)
{
return WidthByIndex(index) * HeightByIndex(index);
}

public BitmapSource ExtractBitmap(int index, List<Color> palette, Color transparency)
{
var segment = new ArraySegment<byte>(RawData, OffsetByIndex(index), ByteSizeByIndex(index));
return Make8bppBitmap(segment.ToArray(), WidthByIndex(index), HeightByIndex(index), palette, transparency);
}

public void OverwriteBitmap(int index, BitmapSource bitmap, IList<Color> palette)
{
if (bitmap.Format != PixelFormats.Indexed8)
throw new NotImplementedException("No palette! PNG format must be indexed with 256 colors (8-bits)");

//Check if palette match
bool match = bitmap.Palette.Colors.Count == palette.Count;
for (int i = 1; i < palette.Count && match; i++) //Skip firt color
if (bitmap.Palette.Colors[i] != palette[i])
match = false;

if (!match)
throw new InvalidDataException("Color palette does not match");

if (bitmap.PixelWidth != WidthByIndex(index)
|| bitmap.PixelHeight != HeightByIndex(index))
throw new ArgumentException("Size mismatch");

int bSize = ByteSizeByIndex(index);
int pos = OffsetByIndex(index);
byte[] pixels = new byte[bSize];
bitmap.CopyPixels(pixels, bitmap.PixelWidth, 0);

for (int i = 0; i < bSize; i++)
RawData[pos + i] = pixels[i];
}

private static BitmapSource Make8bppBitmap(byte[] rawImage, int width, int height, List<Color> palette, Color transparency)
{
int rawStride = (width * PixelFormats.Indexed8.BitsPerPixel + 7) / 8;
if (rawImage.Length != rawStride * height)
throw new ArgumentException("Size mismatch");
palette[0] = transparency;

return BitmapSource.Create(width, height, 96, 96, PixelFormats.Indexed8, new BitmapPalette(palette), rawImage, rawStride);
}
}
}
41 changes: 28 additions & 13 deletions Tools/SPRTYL2PNG/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<DockPanel LastChildFill="True">
<ToolBarTray DockPanel.Dock="Top">
<ToolBar Band="1" >
<Button Content="Open" Click="Open" Name="openButton">
</Button>
<Button Content="Save As..." IsEnabled="False" Name="saveAsButton" Click="SaveAs">
</Button>
<Button Content="Open" Click="Open" Name="openButton" />
<Button Content="Save As..." IsEnabled="False" Name="saveAsButton" Click="SaveAs" />
<Separator/>
<Button Content="Export All as PNG..." IsEnabled="False" Click="ExportAll" Name="exportButton" />
</ToolBar>
<ToolBar Band="2" Name="previewOptionsMenu" IsEnabled="False">
<Label>Palette:</Label>
Expand Down Expand Up @@ -61,15 +61,30 @@
</ToolBar>
</ToolBarTray>
<StatusBar Name="bottomBar" DockPanel.Dock="Bottom" IsEnabled="False">
<Label>Selected Frame:</Label>
<Image Stretch="None" Name="selectedFrame" />
<Label Name="selectedFrameSize" ContentStringFormat="Size: {0}px"/>
<Label Name="selectedFrameIndex" ContentStringFormat="Index: {0}" />
<Button Click="ExportPNGFrame">Export Frame as PNG</Button>
<Button Click="ImportPNGFrame">Replace Frame with PNG</Button>
<Label>Selected:</Label>
<Image Stretch="None" Name="selectedTile" />
<StackPanel>
<Label Name="selectedSpriteSize" ContentStringFormat="Size: {0} px" Padding="5,5,5,0"/>
<Label Name="selectedSpriteIndex" ContentStringFormat="Index: {0}" Padding="5,0,5,0" />
<Label Name="selectedSpriteUnknow" ContentStringFormat="Unknow: {0:x8}" Padding="5,0,5,5" />
</StackPanel>
<Button Click="ExportPNGSprite">Export as PNG</Button>
<Button Name="replaceButton" Click="ImportPNGSprite">Replace with PNG</Button>
</StatusBar>
<ScrollViewer VerticalScrollBarVisibility="Visible">
<WrapPanel Name="previewPanel"/>
</ScrollViewer>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="0" Name="tileColumnDef"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Visible">
<WrapPanel Name="previewPanel"/>
</ScrollViewer>
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Visible">
<WrapPanel Name="previewTilePanel"/>
</ScrollViewer>


</Grid>

</DockPanel>
</Window>
144 changes: 128 additions & 16 deletions Tools/SPRTYL2PNG/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Path = System.IO.Path;

namespace SPRTYL2PNG
{
Expand All @@ -14,7 +15,7 @@ namespace SPRTYL2PNG
public partial class MainWindow : Window
{
string lastOpenedFile;
Sprite loadedSprite;
Graphics loadedGraphics;
Color[] transparencyColor = new Color[]{ Colors.Magenta, Colors.Black, Colors.White };

public MainWindow()
Expand All @@ -25,12 +26,13 @@ public MainWindow()
private void UpdatePreviewPanel()
{
previewPanel.Children.Clear();
previewTilePanel.Children.Clear();

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

var spriteImage = new Image[loadedSprite.Count];
var spriteImage = new Image[loadedGraphics.Quantity];
for (int i = 0; i < spriteImage.Length; i++)
{
spriteImage[i] = new Image();
Expand All @@ -41,17 +43,48 @@ private void UpdatePreviewPanel()
spriteImage[i].Tag = i;
spriteImage[i].MouseDown += (sender, args) =>
{
selectedFrame.Source = ((Image)sender).Source;
selectedFrameIndex.Content = ((Image)sender).Tag;
selectedFrameSize.Content = selectedFrame.Source.Width.ToString() + ";" + selectedFrame.Source.Height.ToString();
selectedTile.Source = ((Image)sender).Source;
selectedSpriteIndex.Content = ((Image)sender).Tag;
selectedSpriteSize.Content = selectedTile.Source.Width.ToString() + ", " + selectedTile.Source.Height.ToString();
selectedSpriteUnknow.Content = loadedGraphics.GetUnknowByIndex((int)((Image)sender).Tag);
bottomBar.IsEnabled = true;
replaceButton.IsEnabled = true;
};
spriteImage[i].SnapsToDevicePixels = true;
spriteImage[i].LayoutTransform = new ScaleTransform(zoomCombo.SelectedIndex + 1, zoomCombo.SelectedIndex + 1);
spriteImage[i].Source = loadedSprite.ToBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
spriteImage[i].Source = loadedGraphics.ExtractBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
previewPanel.Children.Add(spriteImage[i]);
}


var tg = (TileSet)loadedGraphics;
if (tg.TileSetGroupInformation != null)
{
tileColumnDef.Width = new GridLength(250);
var spriteTileImage = new Image[tg.TileSetGroupInformation.Length];
for (int i = 0; i < spriteTileImage.Length; i++)
{
spriteTileImage[i] = new Image();

spriteTileImage[i].Margin = new Thickness(marginsStroke.SelectedIndex * 4);

spriteTileImage[i].Stretch = Stretch.None;
spriteTileImage[i].Tag = i;
spriteTileImage[i].MouseDown += (sender, args) =>
{
selectedTile.Source = ((Image)sender).Source;
selectedSpriteIndex.Content = ((Image)sender).Tag;
selectedSpriteSize.Content = selectedTile.Source.Width.ToString() + ", " + selectedTile.Source.Height.ToString();
selectedSpriteUnknow.Content = tg.GetUnknowByIndex((int)((Image)sender).Tag);
bottomBar.IsEnabled = true;
replaceButton.IsEnabled = false;
};
spriteTileImage[i].SnapsToDevicePixels = true;
spriteTileImage[i].LayoutTransform = new ScaleTransform(zoomCombo.SelectedIndex + 1, zoomCombo.SelectedIndex + 1);
spriteTileImage[i].Source = tg.ExtractTileGroupBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), transparencyColor[transparencyColorPicker.SelectedIndex]);
previewTilePanel.Children.Add(spriteTileImage[i]);
}
}
else tileColumnDef.Width = new GridLength(0);
}

private void Open(object sender, RoutedEventArgs e)
Expand All @@ -60,12 +93,64 @@ private void Open(object sender, RoutedEventArgs e)
openFileDialog.Filter = "Sprite or Tileset Files|*.spr;*.tyl|Sprite Files|*.spr|Tileset Files|*.tyl";
if (openFileDialog.ShowDialog() == true)
{
var fileName = Path.GetFileName(openFileDialog.FileName.ToLower());
bottomBar.IsEnabled = false;
previewOptionsMenu.IsEnabled = true;
lastOpenedFile = openFileDialog.FileName;
exportButton.IsEnabled = true;
lastOpenedFile = openFileDialog.FileName.ToLower();
this.Title = openFileDialog.FileName + " - SPRTYL2PNG";
bool header = System.IO.Path.GetExtension(openFileDialog.FileName).ToUpper() == ".SPR";
loadedSprite = new Sprite(File.OpenRead(openFileDialog.FileName), header);

if(fileName.EndsWith(".spr"))
loadedGraphics = new SpriteSheet(File.ReadAllBytes(openFileDialog.FileName));

else if (fileName.EndsWith(".tyl"))
{
int size = 32;
bool hasHeader = false , hasUnknowTail = false;

switch (fileName)
{
case "medborders.tyl":
size = 20; hasHeader = true;
break;
case "mediumroofs.tyl":
size = 20;
break;
case "lowborders.tyl":
hasHeader = true;
break;
case "locations.tyl":
hasUnknowTail = true;
break;
}

loadedGraphics = new TileSet(hasHeader, hasUnknowTail, size, File.ReadAllBytes(openFileDialog.FileName));

if(fileName == "locations.tyl") // If true, load locations.dat
{
string path = openFileDialog.FileName.ToLower().Replace(".tyl", ".dat");
using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
{
br.BaseStream.Position = 60;
int groupCount = (int)br.ReadInt16() * 4;
TileSetGroupInformation[] tsgi = new TileSetGroupInformation[groupCount];

for (int i = 0; i < groupCount; i++)
{
if (i != 0 && (i & 3) == 0) //Mulltiple of 4, not zero
br.BaseStream.Position++;

tsgi[i] = new TileSetGroupInformation(br);
}

((TileSet)loadedGraphics).TileSetGroupInformation = tsgi;
}


}

}

UpdatePreviewPanel();
}
}
Expand All @@ -76,9 +161,10 @@ private void SaveAs(object sender, RoutedEventArgs e)
saveFileDialog.FileName = lastOpenedFile;
saveFileDialog.Filter = "Sprite Files|*.spr|Tileset Files|*.tyl";
saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(lastOpenedFile);
if (lastOpenedFile.EndsWith(".tyl")) saveFileDialog.FilterIndex = 1;
if (saveFileDialog.ShowDialog() == true)
{
File.WriteAllBytes(saveFileDialog.FileName, loadedSprite.RawData);
File.WriteAllBytes(saveFileDialog.FileName, loadedGraphics.RawData);
this.Title = this.Title.Remove(this.Title.Length - 1);
}
}
Expand All @@ -88,24 +174,26 @@ private void previewOptionsChanged(object sender, SelectionChangedEventArgs e)
UpdatePreviewPanel();
}

private void ExportPNGFrame(object sender, RoutedEventArgs e)
private void ExportPNGSprite(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog exportFileDialog = new Microsoft.Win32.SaveFileDialog();
exportFileDialog.Filter = "PNG-8|*.png";
if (exportFileDialog.ShowDialog() == true)
{
//Create a 8bit PNG with transparency
var bitmap = loadedSprite.ToBitmap(int.Parse(selectedFrameIndex.Content.ToString()), GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0,0,0,0));

//var bitmap = loadedGraphics.ExtractBitmap(int.Parse(selectedSpriteIndex.Content.ToString()), GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0,0,0,0));
var bitmap = selectedTile.Source;
using (var fileStream = new FileStream(exportFileDialog.FileName, FileMode.Create))
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Frames.Add(BitmapFrame.Create(bitmap as BitmapSource));
encoder.Save(fileStream);
}
}
}

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

try
{
loadedSprite.Replace(int.Parse(selectedFrameIndex.Content.ToString()), bs, GamePalettes.ByIndex(palettePicker.SelectedIndex));
loadedGraphics.OverwriteBitmap(int.Parse(selectedSpriteIndex.Content.ToString()), bs, GamePalettes.ByIndex(palettePicker.SelectedIndex));
}
catch (NotImplementedException)
{
Expand Down Expand Up @@ -148,5 +236,29 @@ private void ImportPNGFrame(object sender, RoutedEventArgs e)
}

}

private void ExportAll(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog exportFileDialog = new Microsoft.Win32.SaveFileDialog();
exportFileDialog.Filter = "PNG-8|*.png";
exportFileDialog.Title = "All frames will be exported";
exportFileDialog.FileName = "X.png";
if (exportFileDialog.ShowDialog() == true)
{
for (int i = 0; i < loadedGraphics.Quantity; i++)
{
string exportName = System.IO.Path.GetDirectoryName(exportFileDialog.FileName) + "\\" + i.ToString() + ".PNG";
//Create a 8bit PNG with transparency
var bitmap = loadedGraphics.ExtractBitmap(i, GamePalettes.ByIndex(palettePicker.SelectedIndex), Color.FromArgb(0, 0, 0, 0));
using (var fileStream = new FileStream(exportName, FileMode.Create))
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(fileStream);
}

}
}
}
}
}
Loading

0 comments on commit 79b991c

Please sign in to comment.