Repository navigation
Description
Since I plan on using your better looking icons to replace the ones made with bmp2icon that I've been using until now.

I made a small utility to speed up the process and avoid the need to use a 3D modeling program to replace the texture.
The texture is stored in plain bitmap BGR555 and flipped vertically at offset 0x398 with a length of 0x8000.

So, it is fairly simple to inject a new texture by preparing your new texture swapping red and blue and flipping the image vertically and then saving as a BMP RGB555 (or X1 R5 G5 B5) then getting rid of the BMP header and injecting the raw bitmap.

My utility writes the first part of the 3D icon, then uses imagemagick to take care of the texture and finally spits out a PS2 3D icon, so making textures following your template is all that is needed.
I wrote it using autohotkey, but I imagine it should be easy to port to python since the heavy lifting is done by imagemagick.
Simply place magick.exe alongside the utility and it should work.
example: ps2iconmaker.exe texture_image.png -o ps2icon.icn
ps2iconmaker.zip
Note that this only generates PS2 DVD case icons since that is what I'm most interested in.
The variable iconGeo contains the geometry of the icon. So, changing that with the geometry of the other icons would make it generate the rest.
;@Ahk2Exe-ConsoleApp
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
; SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#ErrorStdOut
global argc
argc = %0%
global argv := []
Loop, % argc
{
argv[A_Index] := %A_Index%
}
If (argc = "0")
{
FileAppend,
(
PS2 Icon Maker
Usage: ps2iconmaker <image> [-o output]
<image>: an image that is 128x128 in size to use as a texture
for more details check the template
[-o output]: filename of the resulting PS2 icon
If an output name is not given, the generated icon will be named ps2icon.ico
),*
ExitApp
}
If RegExMatch(argv[2], "i)^-o$",, 1) and (argc = 2)
{
FileAppend, You need to specify an output filename when using switch -o.`n,**
ExitApp
}
If (! RegExMatch(argv[2], "i)^-o$",, 1)) and (argc > 1)
{
FileAppend, Specify only one input image at a time.`n,**
ExitApp
}
If CheckMagick()
{
FileAppend, magick.exe is missing.`n,**
ExitApp
}
texture := ProcessImage(argv[1])
If (! texture = "temp.bmp")
{
FileAppend, %texture%`n,**
ExitApp
}
If MakeIcon(texture, argv[3])
FileAppend, %errors%`n,**
else
FileAppend, Done.`n,*
ExitApp
;
; Functions
;
CheckMagick()
{
If ! FileExist(A_ScriptDir . "\magick.exe")
Return, 1
Return, 0
}
ProcessImage(img)
{
testimg := FileOpen(img, "r")
If ! IsObject(testimg)
Return, "Cannot open " img "."
testimg.Close()
tempTex := "temp.bmp"
Magick = "%A_ScriptDir%\magick.exe" "%img%" -flip -channel-fx "red <=> blue" -alpha off -define bmp:format=bmp4 -define bmp:subtype=RGB555 "%tempTex%"
RunWait, %Comspec% /c "%Magick%"
Return, tempTex
}
MakeIcon(BMP, output="")
{
If (output = "")
output := "ps2icon.ico"
IconGeo =
(
0000010001000000060000000000803F
2400000072E98EB91C0D0004000097FB
9FF00004030006007F7F7F8072E950F9
D5FA0004000097FB9FF000040300B90B
7F7F7F80BA178EB91C0D0004000097FB
9FF00004130806007F7F7F8072E950F9
D5FA0004000097FB9FF000040300B90B
7F7F7F80BA1750F9D5FA0004000097FB
9FF000041308B90B7F7F7F80BA178EB9
1C0D0004000097FB9FF0000413080600
7F7F7F80BA178EB91C0D000400100000
0000000446027C0E7F7F7F80BA1750F9
D5FA00040010000000000004030E7C0E
7F7F7F80BA17CFBA7B11000400100000
000000044602A00D7F7F7F80BA1750F9
D5FA00040010000000000004030E7C0E
7F7F7F80BA1791FA33FF000400100000
00000004030EA00D7F7F7F80BA17CFBA
7B11000400100000000000044602A00D
7F7F7F80BA17CFBA7B11000400006904
610F0004E90700007F7F7F80BA1791FA
33FF000400006904610F0004E907B90B
7F7F7F8072E9CFBA7B11000400006904
610F0004FC0F00007F7F7F80BA1791FA
33FF000400006904610F0004E907B90B
7F7F7F8072E991FA33FF000400006904
610F0004FC0FB90B7F7F7F8072E9CFBA
7B11000400006904610F0004FC0F0000
7F7F7F8072E9CFBA7B11000400F00000
00000004030E390C7F7F7F8072E991FA
33FF000400F00000000000043C02390C
7F7F7F8072E98EB91C0D000400F00000
00000004030E290D7F7F7F8072E991FA
33FF000400F00000000000043C02390C
7F7F7F8072E950F9D5FA000400F00000
000000043C02290D7F7F7F8072E98EB9
1C0D000400F0000000000004030E290D
7F7F7F8072E9CFBA7B11000400009FF0
690400044904C00F7F7F7F8072E98EB9
1C0D000400009FF0690400044904E60E
7F7F7F80BA17CFBA7B11000400009FF0
69040004590CC00F7F7F7F8072E98EB9
1C0D000400009FF0690400044904E60E
7F7F7F80BA178EB91C0D000400009FF0
69040004590CE60E7F7F7F80BA17CFBA
7B11000400009FF069040004590CC00F
7F7F7F8072E950F9D5FA00040000610F
98FB00044304E30E7F7F7F8072E991FA
33FF00040000610F98FB00044304C60F
7F7F7F80BA1750F9D5FA00040000610F
98FB0004630CE30E7F7F7F8072E991FA
33FF00040000610F98FB00044304C60F
7F7F7F80BA1791FA33FF00040000610F
98FB0004630CC60F7F7F7F80BA1750F9
D5FA00040000610F98FB0004630CE30E
7F7F7F80010000000100000000000000
00000000010000000000000001000000
000000000000803F
)
IconGeo := StrReplace(IconGeo, "`n")
IconLen := StrLen(IconGeo) / 2
Hex2Bin(IconData, IconGeo, IconLen)
BMPFile := FileOpen(BMP, "r")
If ! IsObject(BMPFile)
Return, "Cannot open temporary image."
BMPFile.Seek(0xA)
BMPFile.RawRead(BMPStart, 0x4)
BMPStart := "0x" ChangeEndianess(Bin2Hex(BMPStart, 4))
BMPFile.Seek(BMPStart)
BMPFile.RawRead(BMPTex, 0x8000)
BMPFile.Close()
FileDelete, % BMP
FileDelete, ps2icon.ico
IconICO := FileOpen(output, "a")
If ! IsObject(IconICO)
Return, "Cannot open " output " for writing."
IconICO.RawWrite(IconData, IconLen)
IconICO.RawWrite(BMPTex, 0x8000)
IconICO.Close()
Return, 0
}
Bin2Hex(bin, _byteNb=0)
{
local intFormat, dataSize, dataAddress, granted, x
; Save original integer format
intFormat = %A_FormatInteger%
; For converting bytes to hex
SetFormat Integer, Hex
; Get size of data
dataSize := VarSetCapacity(bin)
if (_byteNb < 1 or _byteNb > dataSize)
_byteNb := dataSize
dataAddress := &bin
; Make enough room (faster)
granted := VarSetCapacity(hex, _byteNb * 2)
if (granted < _byteNb * 2)
{
; Cannot allocate enough memory
ErrorLevel = Mem=%granted%
Return -1
}
Loop %_byteNb%
{
; Get byte in hexa
x := *dataAddress + 0x100
StringRight x, x, 2 ; 2 hex digits
StringUpper x, x
hex = %hex%%x%
dataAddress++ ; Next byte
}
; Restore original integer format
SetFormat Integer, %intFormat%
Return hex
}
Hex2Bin(ByRef @bin, _hex, _byteNb=0)
{
local dataSize, granted, dataAddress, x
; Get size of data
x := StrLen(_hex)
dataSize := Ceil(x / 2)
if (x = 0 or dataSize * 2 != x)
{
; Invalid string, empty or odd number of digits
ErrorLevel = Param
Return -1
}
If (_byteNb < 1 or _byteNb > dataSize)
{
_byteNb := dataSize
}
; Make enough room
granted := VarSetCapacity(@bin, _byteNb, 0)
if (granted < _byteNb)
{
; Cannot allocate enough memory
ErrorLevel = Mem=%granted%
Return -1
}
dataAddress := &@bin
Loop Parse, _hex
{
if (A_Index & 1) ; Odd
{
x = %A_LoopField% ; Odd digit
}
else
{
; Concatenate previous x and even digit, converted to hex
x := "0x" . x . A_LoopField
; Store integer in memory
DllCall("RtlFillMemory"
, "UInt", dataAddress
, "UInt", 1
, "UChar", x)
dataAddress++
}
}
Return _byteNb
}
ChangeEndianess(hex_in)
{
hex_len := StrLen(hex_in) / 2
Loop, % hex_len
{
hex_out .= SubStr(hex_in, 1 + (A_Index * -2), 2)
}
Return, hex_out
}
Activity
CosmicScale commentedon May 4, 2025
Well, that is awesome! Could you please get this working for the other icon types too? Hopefully, we'll see more people making icons if the process is made easier :)
Sakitoshi commentedon May 4, 2025
sure.
ps2iconmaker_v1.1.zip
it works the same as before but added the -t switch to specify an icon type to generate.
just copy imagemagick executable and you are good to go.
ahk source is included in the zip.
ps1 cd case and pal/jp case were the same as ps2 dvd case, with the texture at offset 0x398.
only the ps1 multidisc case was different, i suppose it has more triangles or texture coordinates specified because the texture was further down at offset 0x21F8, same size though. not a huge problem, but worth noting.
Sakitoshi commentedon May 5, 2025
dusted off my fedora laptop and I'm working on a linux script to do the same since imagemagick is on linux too.
Sakitoshi commentedon May 5, 2025
and here it is.
ps2iconmaker_linux.zip
it has the same functionality as the windows version.
it uses imagemagick to convert the image to bitmap then dd to extract the relevant part of the image, after that it concatenates one of the included .icn files, depending on the icon type, with a temporary .tex file to get the desired icon.
so, having imagemagick installed is the only thing needed for it to work.
CosmicScale commentedon May 6, 2025
Amazing! Thank you. When I get some time I'll give them a spin and add them to the templates download :)
CosmicScale commentedon Jun 13, 2025
@Sakitoshi I just pushed a PSBBN update! With the help of your script, the game installer now automatically creates HDD-OSD icons. If a game's cover exists in the OPL Manager Art Database, an icon will be generated. All new icons are automatically contributed to the HDD-OSD Icon Database!
Sakitoshi commentedon Jun 13, 2025
pretty cool.
i thought about it but couldn't think of a good way to determine when the spine starts and ends on the image. so, i gave up on the idea.
CosmicScale commentedon Jun 13, 2025
I'm looking forward to seeing how many icons get generated this weekend!
CosmicScale commentedon Jan 2, 2026
Finally got around to creating a repository for the Icon Generator. I decided to keep it all bash as it works well for Windows users when run from a batch file, just as long as they already have the PSBBN Definitive Patch installed on their PC.
You can find the new icon generator here. This will make generating missing icons much easier. Thanks again for the initial scripts! :)