Author Topic: PHP: Read PMS data?  (Read 6607 times)

0 Members and 1 Guest are viewing this topic.

Offline DePhille

  • Flagrunner
  • ****
  • Posts: 623
  • SoldatPage Webmaster
    • SoldatPage
PHP: Read PMS data?
« on: April 18, 2007, 10:57:39 am »
Hi,

I want to open and read the data out of a PMS file (map) with PHP.
I've seen that Delphi reads the file in a string and then 'automagically' uses it as an array (I don't know anything about Delphi)...
Any idea how can I open the PMS file and use the array with all the map data in PHP?

PS: Also, if you do not know how to do it in PHP but you do know how to do it VB, please explain. I know VB quite well so I can probably derive the PHP code from the VB example.

Thanks.
Grtz, DePhille
This signature was broken. Feel free to fix it.

Offline FliesLikeABrick

  • Administrator
  • Flamebow Warrior
  • *****
  • Posts: 6144
    • Ultimate 13 Soldat
Re: PHP: Read PMS data?
« Reply #1 on: April 18, 2007, 11:35:39 am »
Someone on here already has some code to do this (Docs, maybe?).  Hopefully this topic will get their attention and they can help you out.

Offline truup

  • Soldier
  • **
  • Posts: 243
Re: PHP: Read PMS data?
« Reply #2 on: April 18, 2007, 12:07:01 pm »
As far as I know, Doc ain't gonna give it away, that's what he said on one topic: http://forums.soldat.pl/index.php?topic=11121.msg132269#msg132269

Here's file structure -code for C++ anyway: http://wiki.soldat.nl/Maps

Offline DePhille

  • Flagrunner
  • ****
  • Posts: 623
  • SoldatPage Webmaster
    • SoldatPage
Re: PHP: Read PMS data?
« Reply #3 on: April 18, 2007, 03:30:05 pm »
Thanks for the answers so far.
I doubt if I'm able to find my trough though, so any further help is much appreciated.

I think holding back codes like this is preet selfish and once I know how to do it in PHP I'll share it with the community!

Grtz, DePhille
This signature was broken. Feel free to fix it.

Offline chrisgbk

  • Inactive Staff
  • Veteran
  • *****
  • Posts: 1739
Re: PHP: Read PMS data?
« Reply #4 on: April 19, 2007, 01:25:56 am »
I have code to read the data in VB; let me dig it out.

First off, a note; Delphi doesn't load it into a string at all; if you look at the code in the mapmaker, there is a function for loading maps (differentiate between the one that loads PMS files and the one that loads the old PMP file format, the old one used a static sized type that was the same for every map, so every map was the same size. The new code is a bit different, in that maps have variable sizes)

Helper functions for file access (to allow the code to look a bit cleaner):
Code: [Select]
Option Explicit

Public Sub writeByte(filenumber As Integer, value As Byte)
    Put #filenumber, , value
End Sub

Public Sub writeInteger(filenumber As Integer, value As Integer)
    Put #filenumber, , value
End Sub

Public Sub writeLong(filenumber As Integer, value As Long)
    Put #filenumber, , value
End Sub

Public Sub writeSingle(filenumber As Integer, value As Single)
    Put #filenumber, , value
End Sub

Public Sub writeDelphiString(filenumber As Integer, value As String)
    writeByte filenumber, CByte(Len(Trim(value)))
    Put #filenumber, , value
End Sub

Public Function getDelphiString(ByVal filenumber As Integer, ByVal maxSize As Byte) As String
    getDelphiString = Space(maxSize + 1) 'include length byte
    Get #filenumber, , getDelphiString
    getDelphiString = Mid(getDelphiString, 2, Asc(Left(getDelphiString, 1))) 'use length byte to cut off end
End Function

Public Function getByte(ByVal filenumber As Integer) As Byte
    Get #filenumber, , getByte
End Function

Public Function getInteger(ByVal filenumber As Integer) As Integer
    Get #filenumber, , getInteger
End Function

Public Function getLong(ByVal filenumber As Integer) As Long
    Get #filenumber, , getLong
End Function

Public Function getSingle(ByVal filenumber As Integer) As Single
    Get #filenumber, , getSingle
End Function

Public Sub Skip(ByVal filenumber As Integer, ByVal numBytes As Long)
    Seek #filenumber, Loc(filenumber) + numBytes + 1
End Sub

Types
Code: [Select]
'D3DVECTOR2 is defined as a type with X as single, Y as single
Public Type tColor
    blue As Byte
    green As Byte
    red As Byte
    alpha As Byte
End Type

Public Type tHeader
    version As Long
End Type

Public Type tCollider
    Active As Byte
    x As Single
    y As Single
    'Coords As D3DVECTOR2
    translation As D3DVECTOR2
    radius As Single
    Scale As D3DVECTOR2
End Type

Public Type tOptions
    MapName As String * MAPNAME_SIZE
    TextureName As String * TEXTURENAME_SIZE
    BackgroundColor As tColor
    BackgroundColor2 As tColor
    StartJet As Long
    GrenadePacks As Byte
    Medikits As Byte
    Weather As Byte
    Steps As Byte
    RandomID As Long
End Type

Public Type tProp
    Active As Byte
    Style As Integer
    width As Long
    height As Long
    'Corner As Point
    x As Single
    y As Single
    'Coords As D3DVECTOR2
    translation As D3DVECTOR2
    rotation As Single
    ScaleX As Single
    ScaleY As Single
    'OriginalScale As D3DVECTOR2
    Scale As D3DVECTOR2
    color As tColor
    level As Byte
End Type

Public Type tScenery
    SceneryName As String * SCENERY_SIZE
    lDate As Long
End Type

Public Type tSector
    PolyCount As Integer
    Polys() As Integer
End Type

Public Type tSpawnpoint
    Active As Byte
    x As Long
    y As Long
    team As Long
End Type

Public Type tWaypoint
    Active As Byte
    id As Long
    x As Long
    y As Long
    Left As Byte
    Right As Byte
    up As Byte
    down As Byte
    m2 As Byte
    PathNum As Byte
    C1 As Byte
    C2 As Byte
    C3 As Byte
    ConnectionsNum As Long
    connections(MAX_CONNECTIONS) As Long
End Type

Public Type tVertex
    x As Single
    y As Single
    Z As Single
    RHW As Single '// Position for the vertex.
    color As tColor '  // Diffuse color.
    tu As Single
    tv As Single
End Type

Public Type tPolygon
   vertices() As tVertex
   Perp(1 To 3) As D3DVECTOR
   PolyType As Byte
End Type

Map reading helper functions
Code: [Select]
'with this code, I chose to, instead of padding the types, to load them all manually. While it means slower loading times, it means much clearer code and less useless variables.
Public Function getWaypoint(filenumber As Integer) As tWaypoint
    Dim j As Byte
    With getWaypoint
        .Active = getByte(filenumber)
        Skip filenumber, 3
        .id = getLong(filenumber)
        .x = getLong(filenumber)
        .y = getLong(filenumber)
        .Left = getByte(filenumber)
        .Right = getByte(filenumber)
        .up = getByte(filenumber)
        .down = getByte(filenumber)
        .m2 = getByte(filenumber)
        .num = 1 + .up + .down * 2 + .Left * 4 + .Right * 8 + .m2 * 16
        .PathNum = getByte(filenumber)
        .C1 = getByte(filenumber)
        .C2 = getByte(filenumber)
        .C3 = getByte(filenumber)
        Skip filenumber, 3
        .ConnectionsNum = getLong(filenumber)
        For j = 1 To MAX_CONNECTIONS
            If j <= .ConnectionsNum Then
                .connections(j) = getLong(filenumber)
            Else
                Skip filenumber, 4
            End If
        Next j
    End With
End Function

Public Function getSpawnpoint(filenumber As Integer) As tSpawnpoint
    With getSpawnpoint
        .Active = getByte(filenumber)
        Skip filenumber, 3
        .x = getLong(filenumber)
        .y = getLong(filenumber)
        .team = getLong(filenumber)
    End With
End Function

Public Function getCollider(filenumber As Integer) As tCollider
    With getCollider
        .Active = getByte(filenumber)
        Skip filenumber, 3
        .x = getSingle(filenumber)
        .y = getSingle(filenumber)
        .radius = getSingle(filenumber)
    End With
End Function

Public Function getScenery(filenumber As Integer) As tScenery
    With getScenery
        .SceneryName = getDelphiString(filenumber, SCENERY_SIZE)
        .lDate = getLong(filenumber)
    End With
End Function

Public Function getProp(filenumber As Integer) As tProp
    Dim alpha As Byte
    With getProp
        .Active = getByte(filenumber)
        Skip filenumber, 1
        .Style = getInteger(filenumber)
        .width = getLong(filenumber)
        .height = getLong(filenumber)
        .x = getSingle(filenumber)
        .y = getSingle(filenumber)
        .rotation = getSingle(filenumber)
        .ScaleX = getSingle(filenumber)
        .ScaleY = getSingle(filenumber)
        alpha = getByte(filenumber)
        Skip filenumber, 3
        .color = getColor(filenumber)
        .color.alpha = alpha
        .level = getByte(filenumber)
        Skip filenumber, 3
    End With
End Function

Public Function getPolygon(filenumber As Integer) As tPolygon
    Dim k As Long
    With getPolygon
        'loop through vertices
        ReDim .vertices(4)
        ReDim .basevertices(3)
        For k = 1 To 3
            'deal with this vertice
            .vertices(k) = getVertex(filenumber)
            .basevertices(k).x = .vertices(k).x
            .basevertices(k).y = .vertices(k).y
            'Debug.Print .vertices(k).x
            '.Vertex(k) = tempVertex
            'done with this vertice
        Next k
        .vertices(k) = .vertices(1)
        'next vertice
        'loop through the perps
        For k = 1 To 3
            'load each perp
            .Perp(k) = getVector(filenumber)
            'Debug.Print i, .Perp(k).x, .Perp(k).y, .Perp(k).Z
        Next k
        'next perp
        'get the type of this polygon
       
        .PolyType = getByte(filenumber)
    End With
End Function

Public Function getOptions(filenumber As Integer) As tOptions
    With getOptions
        ' convert map name string
        .MapName = getDelphiString(filenumber, MAPNAME_SIZE)
        ' convert texture string
        .TextureName = getDelphiString(filenumber, TEXTURENAME_SIZE)
        'load top background color a byte at a time
        .BackgroundColor = getColor(filenumber)
        .BackgroundColor2 = getColor(filenumber)
        'load bottom background color a byte at a time
        .StartJet = getLong(filenumber)
        .GrenadePacks = getByte(filenumber)
        .Medikits = getByte(filenumber)
        .Weather = getByte(filenumber)
        .Steps = getByte(filenumber)
        .RandomID = getLong(filenumber)
    End With
End Function

Public Function getColor(filenumber As Integer) As tColor
    With getColor
        .blue = getByte(filenumber)
        .green = getByte(filenumber)
        .red = getByte(filenumber)
        .alpha = getByte(filenumber)
    End With
End Function

Public Function getVector(ByVal filenumber As Integer) As D3DVECTOR
    Get #filenumber, , getVector
End Function

Public Function getVertex(ByVal filenumber As Integer) As tVertex
    Get #filenumber, , getVertex
End Function


The actual map loading function
Code: [Select]
Public Sub openFile(rFilePath As String)
    If rFilePath = "" Then Exit Sub
    filePath = rFilePath
    'Counters for array access
    Dim i As Integer
    Dim k As Integer
    Dim j As Integer
    Dim tempVector As D3DVECTOR
    Dim tempVertex As tVertex
    Dim tempLong As Long
    Dim FN As Integer
    FN = FreeFile
    ' open map
    Open rFilePath For Binary Access Read Lock Write As #FN
    with Header
        .version = getLong(FN)
    End With
    Options = getOptions(FN)
    ReDim Polygon(getLong(FN))
    For i = 1 To UBound(Polygon)
        Polygon(i) = getPolygon(FN)
    Next i
    SectorDivision = getLong(FN)
    Skip FN, 4
    'this is a 50x50 array in essence, -25 to 25
    For i = -NUM_SECTORS To NUM_SECTORS
        For j = -NUM_SECTORS To NUM_SECTORS
            'In my original code, I skipped sector data; when saving it was useless because it had to be regenerated to account for changes. I've included some code in here for your benefit.
            'Skip FN, getInteger(FN) * 2
            Sector(i,j).Polycount = getInteger(FN)
            redim Sector(i,j).Polys(Sector(i,j).Polycount)
            For k = 1 To Sector(i,j).Polycount
                sector(i,j).Polys(k) = getInteger(FN) 'this is the index of the poly in that sector
            Next k
        Next j
    Next i
    tempLong = getLong(FN)
    If tempLong > 0 Then
        ReDim Prop(tempLong)
    Else
        ReDim Prop(0 To 0)
    End If
    For i = 1 To UBound(Prop)
        Prop(i) = getProp(FN)
    Next i
    tempLong = getLong(FN)
    If tempLong > 0 Then
        ReDim Scenery(tempLong)
    Else
        ReDim Scenery(0 To 0)
    End If
    For i = 1 To UBound(Scenery)
        Scenery(i) = getScenery(FN)
    Next i
    tempLong = getLong(FN)
    If tempLong > 0 Then
        ReDim Collider(tempLong)
    Else
        ReDim Collider(0 To 0)
    End If
    For i = 1 To UBound(Collider)
        Collider(i) = getCollider(FN)
    Next i
    tempLong = getLong(FN)
    If tempLong > 0 Then
        ReDim Spawnpoint(tempLong)
    Else
        ReDim Spawnpoint(0 To 0)
    End If
    For i = 1 To UBound(Spawnpoint)
        Spawnpoint(i) = getSpawnpoint(FN)
    Next i
    tempLong = getLong(FN)
    If tempLong > 0 Then
        ReDim Waypoint(tempLong)
    Else
        ReDim Waypoint(0 To 0)
    End If
    For i = 1 To UBound(Waypoint)
        Waypoint(i) = getWaypoint(FN)
    Next i
    Close #FN
End Sub

Note that this is by no means 100% complete, accurate, or without errors; use at your own risk. The basic principle is there however, so it should guide you. I used to have code for stepping through the file by offset rather than using types; that may be useful to you as well, but I'm not sure where it is anymore. If you need it, let me know, and I can re-write it as it isn't that difficult to do.

This code is quite old as well, as it was written before Polyworks (Polyworks was the reason why I stopped working on this code; it was far better than what I was working on so there wasn't a point in continuing).
« Last Edit: April 19, 2007, 02:17:58 am by chrisgbk »

Offline FliesLikeABrick

  • Administrator
  • Flamebow Warrior
  • *****
  • Posts: 6144
    • Ultimate 13 Soldat
Re: PHP: Read PMS data?
« Reply #5 on: April 19, 2007, 02:36:24 am »
If I have some time, I'll try to write a code snippet for PHP that shows a good way to work through the file using offsets.

What I would probably do is this:

Write a small class that is created with a filename as its only argument.  The class would then have a few methods to move forwards (and backwards, just so that you can re-read stuff) through the file by specifying offsets.  Example:

Code: [Select]
<?php
// create the object to help us read from the PMS data
$pmshandle = new PMSHandle('someMap.pms');

// read the first 3 bytes (byte 0, 1, and 2) of the file into $first3bytes
$first3bytes $pmshandle->read(3);

// read the second and third bytes (1 and 2) into the $twobytes
$twobytes $pmshandle->back(2);


?>


I would have the class object keep track of where it is in the file, and use whatever php functions are available read and move the specified offset in either direction, returning the data stored between the current position and that offset.

Now that I think of it, just $pmshandle->read() would be enough if it was made in such a way that it could handle negative offsets, but it couldn't hurt to have a separate method just for added clarity.

Yes something like this is small and stupidly simple, but it is small things like this that helped me make my PHP ASE parser so efficient and easy to code.  If someone is going to be doing this in PHP, it'd probably help to create a tiny-ass wrapper like this just to make it much easier to do.
« Last Edit: April 19, 2007, 02:41:30 am by FliesLikeABrick »

Offline truup

  • Soldier
  • **
  • Posts: 243
Re: PHP: Read PMS data?
« Reply #6 on: April 19, 2007, 08:19:11 am »
Here's also a link to EditPro, which is mapmaker coded in VB6, you can download the source too: http://koti.mbnet.fi/meitzi/EditPro/

Though im not sure if it's made for the new map format, it seems to be a few years old.
« Last Edit: April 19, 2007, 08:21:32 am by truup »

Offline DePhille

  • Flagrunner
  • ****
  • Posts: 623
  • SoldatPage Webmaster
    • SoldatPage
Re: PHP: Read PMS data?
« Reply #7 on: April 19, 2007, 09:53:22 am »
Alright, thanks to all so far :).
All the tips are pretty useful, and I can atleast start coding now. I don't think there'll be more difficulties for writing this script, but if you still know tips and tricks or hints, feel free to post them because it will definitely speed up the coding!

Grtz, DePhille
This signature was broken. Feel free to fix it.

Offline chrisgbk

  • Inactive Staff
  • Veteran
  • *****
  • Posts: 1739
Re: PHP: Read PMS data?
« Reply #8 on: April 19, 2007, 09:35:40 pm »
Here's also a link to EditPro, which is mapmaker coded in VB6, you can download the source too: http://koti.mbnet.fi/meitzi/EditPro/

Though im not sure if it's made for the new map format, it seems to be a few years old.

That's made for the old map format; not only that, but it's written entirely in Finish, so unless you know the language it's useless for you.

Date Posted: April 19, 2007, 06:30:04 PM
If I have some time, I'll try to write a code snippet for PHP that shows a good way to work through the file using offsets.

What I would probably do is this:

Write a small class that is created with a filename as its only argument.  The class would then have a few methods to move forwards (and backwards, just so that you can re-read stuff) through the file by specifying offsets.  Example:

Code: [Select]
<?php
// create the object to help us read from the PMS data
$pmshandle = new PMSHandle('someMap.pms');

// read the first 3 bytes (byte 0, 1, and 2) of the file into $first3bytes
$first3bytes $pmshandle->read(3);

// read the second and third bytes (1 and 2) into the $twobytes
$twobytes $pmshandle->back(2);


?>


I would have the class object keep track of where it is in the file, and use whatever php functions are available read and move the specified offset in either direction, returning the data stored between the current position and that offset.

Now that I think of it, just $pmshandle->read() would be enough if it was made in such a way that it could handle negative offsets, but it couldn't hurt to have a separate method just for added clarity.

Yes something like this is small and stupidly simple, but it is small things like this that helped me make my PHP ASE parser so efficient and easy to code.  If someone is going to be doing this in PHP, it'd probably help to create a tiny-ass wrapper like this just to make it much easier to do.

I wouldn't recommend that approach for PMS files, due to the way they are structured. Anyhow, to find the offsets of each part requires something like the following:

Code: [Select]
<?php

  Â  Â  Â  $filehandle 
fopen($filename"rb");
  Â  Â  Â  $polygonoffset 92;
  Â  Â  Â  fseek($filehandle88);
  Â  Â  Â  fseek($filehandle, (((int)fread($filehandle4)) * 79) + 4SEEK_CUR); //skip polygon data and pixel size of sectors
  Â  Â  Â  $sectoroffset ftell($filehandle) + 4;
  Â  Â  Â  $numsectors = (int)fread($filehandle4)
  Â  Â  Â  for ($i = -$numsectors$i <= $numsectors$i++) {
  Â  Â  Â  Â  Â  for ($j = -$numsectors$j <= $numsectors$j++) {
  Â  Â  Â  Â  Â  Â  Â  //$polysinsector = (int)fread($filehandle, 2);
  Â  Â  Â  Â  Â  Â  Â  //for ($k = 0; $k < $polysinsector; $k++) {
  Â  Â  Â  Â  Â  Â  Â  //  Â  $polyindex = (int)fread($filehandle, 2);
  Â  Â  Â  Â  Â  Â  Â  //}
  Â  Â  Â  Â  Â  Â  Â  fseek($filehandle, ((int)fread($filehandle2)) * 2SEEK_CUR); //skips over sector data
  Â  Â  Â  Â  Â  }
  Â  Â  Â  }
  Â  Â  Â  $propoffset ftell($filehandle) + 4;
  Â  Â  Â  fseek($filehandle, (((int)fread($filehandle4)) * 44), SEEK_CUR); //skip over prop data
  Â  Â  Â  $sceneryoffset ftell($filehandle) + 4;
  Â  Â  Â  fseek($filehandle, (((int)fread($filehandle4)) * 55), SEEK_CUR); //skip over scenery data
  Â  Â  Â  $collideroffset ftell($filehandle) + 4;
  Â  Â  Â  fseek($filehandle, (((int)fread($filehandle4)) * 16), SEEK_CUR); //skip over collider data
  Â  Â  Â  $spawnpointoffset ftell($filehandle) + 4;
  Â  Â  Â  fseek($filehandle, (((int)fread($filehandle4)) * 16), SEEK_CUR); //skip over spawnpoint data
  Â  Â  Â  $waypointoffset ftell($filehandle) + 4;
  Â  Â  Â  //waypoint data is last section, don't need to skip it

(I make no guarantee that this code will work at all, it's for illustrative purposes only)

But, I find it much more efficient to load all the data as you parse it, rather than use offsets explicitly; the only time the above approach becomes useful is when you want access to a specific section of data and want to skip other parts of it rather than parsing it.

You also need to be aware of other issues with PMS data; various parts of it are padded so you will have to at times skip the padded areas.
« Last Edit: April 19, 2007, 09:41:55 pm by chrisgbk »