Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
S
SRB2-Query
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
James R.
SRB2-Query
Commits
21cd4679
Commit
21cd4679
authored
5 years ago
by
James R.
Browse files
Options
Downloads
Patches
Plain Diff
First release
parents
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
srb2query.php
+655
-0
655 additions, 0 deletions
srb2query.php
util.php
+75
-0
75 additions, 0 deletions
util.php
with
730 additions
and
0 deletions
srb2query.php
0 → 100644
+
655
−
0
View file @
21cd4679
<?php
/*
Copyright 2019, James R.
All rights reserved.
Redistribution and use in source form, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of this project.
*/
/*
class SRB2Query::
function SetTimeout (int $milliseconds) : bool
Set timeout for packet receiving. If a connection is already open, the
an attempt is made to apply the timeout. FALSE is returned if something
happens! The default timeout is two milliseconds.
function SetRetries (int $tries) : void
Set the number of retries after an invalid packet is received and until a
good packet or receive times out. By default, NO MERCY.
function Close () : void
Free resources. The timeout and retry count persist.
function Ask (string $host, int $port) : bool
Ask a server for server info and player info. FALSE is returned if something
bad happened.
function Info (string &$addr) : array
Return an array of serverinfo and playerinfo. The IP address that was asked
is returned in addr.
The array has the following keys:
'version' => [
'major' => (int),
'minor' => (int),
'patch' => (int),
'name' => (string),
Name of the mod--SRB2 or SRB2Kart.
],
'servername' => (string),
'mods' => (bool),
'cheats' => (bool),
'dedicated' => (bool),
'password-protected' => (bool),
'players' => [
'count' => (int),
Players currently connected.
'max' => (int),
The limit of players allowed to connect.
'list' => [
[
'name' => (string),
'team' => (string),
'Red' and 'Blue' in CTF, 'Spectator' when spectating, and
'Playing' otherwise.
'score' => (int),
SRB2 only; POINTS!
'rank' => (int),
SRB2Kart only; Advancing rank over races.
'seconds' => (int),
Number of seconds since the player joined the server.
]...
],
],
'level' => [
'seconds' => (int),
Numbers of seconds since the level started.
'title' => (string),
Inlcuding ZONE and the act number (if either applicable).
'mapmd5' => (string),
The map MD5 checksum in hexadecimal representation.
],
'gametype' => (string),
'kartspeed' => (string),
SRB2Kart Race gametype only; The racing speed.
function FileInfo () : array
Return an array of files added to server previously queried with
SRB2Query::Ask with the following keys.
[
'name' => (string),
'bytes' => (int),
'md5sum' => (string),
The file's MD5 checksum in hexadecimal representation.
'toobig' => (bool),
The file is too big so server is not willing to send it.
'download' => (bool),
The server is willing to send the file.
]...
function Colorize (string $string) : string
Convert SRB2 byte color codes and caret codes to CSS colored span tags in
string and return it. See SRB2Query::$colors for the list of colors.
array $colors
The list of SRB2 name colors.
*/
include
__DIR__
.
'/util.php'
;
// THE BIG CLASS
class
SRB2Query
{
public
$colors
=
[
'inherit'
,
'#df00df'
,
'#ffff0f'
,
'#69e046'
,
'#7373ff'
,
'#ff3f3f'
,
'#a7a7a7'
,
'#ff9736'
,
'#55c8ff'
,
'#cf7fcf'
,
'#d7bb43'
,
'#c7e494'
,
'#c4c4e1'
,
'#f3a3a3'
,
'#bf7b4b'
,
'#ffc7a7'
,
];
// doomdef.h
private
const
TICRATE
=
35
;
// doomstat.h
private
const
GT_COOP
=
0
;
private
const
GT_COMPETITION
=
1
;
private
const
GT_RACE
=
2
;
private
const
GT_MATCH
=
3
;
private
const
GT_TEAMMATCH
=
4
;
private
const
GT_TAG
=
5
;
private
const
GT_HIDEANDSEEK
=
6
;
private
const
GT_CTF
=
7
;
private
const
GF_REDFLAG
=
1
;
private
const
GF_BLUEFLAG
=
2
;
// d_clisrv.h
private
const
PT_ASKINFO
=
12
;
private
const
PT_SERVERINFO
=
13
;
private
const
PT_PLAYERINFO
=
14
;
private
const
PT_TELLFILESNEEDED
=
34
;
private
const
PT_MOREFILESNEEDED
=
35
;
private
const
SV_SPEEDMASK
=
0x03
;
private
const
SV_LOTSOFADDONS
=
0x20
;
private
const
SV_DEDICATED
=
0x40
;
private
const
SV_PASSWORD
=
0x80
;
// d_netfil.c
private
const
NETFIL_WONTSEND
=
32
;
private
const
NETFIL_WILLSEND
=
16
;
private
const
pkformats
=
[
self
::
PT_SERVERINFO
=>
[
'format'
=>
'Cversion/'
.
'Csubversion/'
.
'Cnumberofplayer/'
.
'Cmaxplayer/'
.
'Cgametype/'
.
'Cmodifiedgame/'
.
'Ccheatsenabled/'
.
'Cisdedicated/'
.
'Cfileneedednum/'
.
'cadminplayer/'
.
'Vtime/'
.
'Vleveltime/'
.
'a32servername/'
.
'a8mapname/'
.
'a33maptitle/'
.
'a16mapmd5/'
.
'Cactnum/'
.
'Ciszone/'
.
'a*fileneeded'
,
'strings'
=>
[
'servername'
,
'mapname'
,
'maptitle'
,
],
'minimum'
=>
109
,
],
self
::
PT_PLAYERINFO
=>
[
'format'
=>
'Cnode/'
.
'a22name/'
.
'a4address/'
.
'Cteam/'
.
'Cskin/'
.
'Cdata/'
.
'Vscore/'
.
'vtimeinserver'
,
'strings'
=>
[
'name'
,
],
'minimum'
=>
36
,
],
self
::
PT_MOREFILESNEEDED
=>
[
'format'
=>
'Vfirst/'
.
'Cnum/'
.
'Cmore/'
.
'a*files'
,
'minimum'
=>
6
,
],
];
// w_wad.h
private
const
MAX_WADPATH
=
512
;
private
$so
;
private
$addr
,
$port
;
private
$timeout
=
[
'sec'
=>
2
,
'usec'
=>
0
,
];
private
$retries
=
0
;
private
$lotsofaddons
;
private
$fileneedednum
;
private
$fileneeded
;
private
function
Versionname
(
$pk
)
{
switch
(
$pk
[
'version'
])
{
case
100
:
case
110
:
return
'SRB2Kart'
;
default
:
return
'SRB2'
;
}
}
private
function
Gametype
(
$pk
)
{
// SRB2Kart
if
(
$pk
[
'version'
]
==
100
||
$pk
[
'version'
]
==
110
)
{
$kartnames
=
[
self
::
GT_COMPETITION
=>
'Battle'
,
];
if
(
isset
(
$kartnames
[
$pk
[
'gametype'
]]))
return
$kartnames
[
$pk
[
'gametype'
]];
}
$names
=
[
self
::
GT_COOP
=>
'Co-op'
,
self
::
GT_COMPETITION
=>
'Competition'
,
self
::
GT_RACE
=>
'Race'
,
self
::
GT_MATCH
=>
'Match'
,
self
::
GT_TEAMMATCH
=>
'Team Match'
,
self
::
GT_TAG
=>
'Tag'
,
self
::
GT_HIDEANDSEEK
=>
'Hide and Seek'
,
self
::
GT_CTF
=>
'CTF'
,
];
$name
=
$names
[
$pk
[
'gametype'
]];
return
(
(
$name
)
?
$name
:
'Unknown'
);
}
private
function
Zonetitle
(
$pk
)
{
$maptitle
=
$pk
[
'maptitle'
];
if
(
$pk
[
'iszone'
])
$maptitle
.
=
' ZONE'
;
if
(
$pk
[
'actnum'
])
$maptitle
.
=
' '
.
$pk
[
'actnum'
];
return
$maptitle
;
}
private
function
Unkartvars
(
&
$info
,
$pk
)
{
$c
=
$pk
[
'isdedicated'
];
$this
->
lotsofaddons
=
(
$c
&
self
::
SV_LOTSOFADDONS
);
if
(
$pk
[
'gametype'
]
==
self
::
GT_RACE
)
{
$speeds
=
[
'Easy'
,
'Normal'
,
'Hard'
,
];
$speed
=
$speeds
[(
$c
&
self
::
SV_SPEEDMASK
)];
$info
[
'kartspeed'
]
=
(
(
$speed
)
?
$speed
:
'Too fast'
);
}
copy_bool
(
$info
[
'dedicated'
],
(
$c
&
self
::
SV_DEDICATED
));
copy_bool
(
$info
[
'password-protected'
],
(
$c
&
self
::
SV_PASSWORD
));
}
private
function
Checksum
(
$p
,
$l
)
{
$n
=
strlen
(
$p
)
-
$l
;
$c
=
0x1234567
;
for
(
$i
=
0
;
$i
<
$n
;
++
$i
)
$c
+=
ord
(
$p
[
$l
+
$i
])
*
(
$i
+
1
);
return
$c
;
}
private
function
Packet
(
$pk
)
{
switch
(
$pk
[
'type'
])
{
case
self
::
PT_ASKINFO
:
/* 1 byte version and 4 byte time */
$u
=
pack
(
'x5'
);
break
;
case
self
::
PT_TELLFILESNEEDED
:
$u
=
pack
(
'V'
,
$pk
[
'filesneedednum'
]);
break
;
}
/* 1 byte ack and 1 byte ackreturn, finally 1 byte padding */
$buf
=
pack
(
'xxCx'
,
$pk
[
'type'
])
.
$u
;
return
pack
(
'V'
,
self
::
Checksum
(
$buf
,
0
))
.
$buf
;
}
private
function
Unpk
(
$pk
,
$n
=
0
)
{
$pkf
=
self
::
pkformats
[
$pk
[
'type'
]];
// I can get away with just 'minimum' for now because there aren't any
// variable length packets in array format. That sounds stupid anyway.
$n
=
$n
*
$pkf
[
'minimum'
];
if
(
$n
>
strlen
(
$pk
[
'buffer'
]))
return
FALSE
;
$t
=
unpack
(
$pkf
[
'format'
],
$pk
[
'buffer'
],
$n
);
if
(
isset
(
$pkf
[
'strings'
]))
{
foreach
(
$pkf
[
'strings'
]
as
$str
)
$t
[
$str
]
=
cstr
(
$t
[
$str
]);
}
return
$t
;
}
private
function
Unpacket
(
$p
,
$type
,
$unpk
=
TRUE
)
{
$n
=
strlen
(
$p
);
if
(
$n
<
8
)
// Header
return
FALSE
;
if
(
unpack
(
'V'
,
$p
)[
1
]
!=
self
::
Checksum
(
$p
,
4
))
// Checksum mismatch
return
FALSE
;
if
((
$pk
[
'type'
]
=
ord
(
$p
[
6
])
)
!=
$type
)
return
FALSE
;
if
(
!
(
$pkf
=
self
::
pkformats
[
$pk
[
'type'
]]
))
return
FALSE
;
if
(
$n
<
$pkf
[
'minimum'
])
return
FALSE
;
$pk
[
'buffer'
]
=
substr
(
$p
,
8
);
if
(
$unpk
)
{
$pk
=
array_merge
(
$pk
,
self
::
Unpk
(
$pk
));
unset
(
$pk
[
'buffer'
]);
}
return
$pk
;
}
private
function
Unfileneeded
(
&
$fileinfo
,
$fileneedednum
,
$fileneeded
)
{
$l
=
0
;
for
(
$i
=
0
;
$i
<
$fileneedednum
;
++
$i
)
{
$pk
=
unpack
(
'Cstatus/'
.
'Vsize'
,
$fileneeded
,
$l
);
$l
+=
5
;
$pk
[
'name'
]
=
cstr
(
$fileneeded
,
$l
,
self
::
MAX_WADPATH
);
$l
+=
cstrsize
(
$fileneeded
,
$l
,
self
::
MAX_WADPATH
);
$pk
[
'md5sum'
]
=
bin2hex
(
substr
(
$fileneeded
,
$l
,
16
));
$l
+=
16
;
copy_bool
(
$pk
[
'toobig'
],
!
(
$pk
[
'status'
]
&
self
::
NETFIL_WILLSEND
));
copy_bool
(
$pk
[
'download'
],
(
$pk
[
'toobig'
]
||
(
$pk
[
'status'
]
&
self
::
NETFIL_WONTSEND
)
));
unset
(
$pk
[
'status'
]);
$fileinfo
[]
=
$pk
;
}
}
private
function
Send
(
$pk
)
{
$buf
=
self
::
Packet
(
$pk
);
$b
=
strlen
(
$buf
);
$n
=
socket_sendto
(
$this
->
so
,
$buf
,
$b
,
0
,
$this
->
addr
,
$this
->
port
);
return
(
$n
==
$b
);
}
private
function
Settimeoutopt
()
{
return
socket_set_option
(
$this
->
so
,
SOL_SOCKET
,
SO_RCVTIMEO
,
$this
->
timeout
);
}
private
function
Sendto
(
$host
,
$port
,
$pk
)
{
if
(
!
$this
->
so
)
{
$this
->
so
=
socket_create
(
AF_INET
,
SOCK_DGRAM
,
SOL_UDP
);
if
(
!
(
$this
->
so
&&
self
::
Settimeoutopt
()
))
return
FALSE
;
}
$this
->
addr
=
gethostbyname
(
$host
);
$this
->
port
=
$port
;
return
self
::
Send
(
$pk
);
}
private
function
Read
(
$type
,
$unpk
=
TRUE
)
{
$tries
=
0
;
do
{
$n
=
socket_recvfrom
(
$this
->
so
,
$buf
,
1450
,
0
,
$addr
,
$port
);
if
(
$n
===
FALSE
||
$addr
!=
$this
->
addr
||
$port
!=
$this
->
port
)
{
return
FALSE
;
}
$pk
=
self
::
Unpacket
(
$buf
,
$type
,
$unpk
);
}
while
(
$pk
===
FALSE
&&
$tries
++
<
$this
->
retries
)
;
return
$pk
;
}
function
SetTimeout
(
int
$ms
)
:
bool
{
$this
->
timeout
=
[
'sec'
=>
$ms
/
1000
,
'usec'
=>
$ms
%
1000
*
1000
,
];
if
(
$this
->
so
)
return
self
::
Settimeoutopt
();
else
return
TRUE
;
}
function
SetRetries
(
int
$n
)
:
void
{
$this
->
retries
=
$n
;
}
function
Close
()
:
void
{
if
(
$this
->
so
)
socket_close
(
$this
->
so
);
unset
(
$this
->
addr
,
$this
->
port
,
$this
->
lotsofaddons
,
$this
->
fileneedednum
,
$this
->
fileneeded
);
}
function
Ask
(
string
$host
,
int
$port
)
:
bool
{
return
self
::
Sendto
(
$host
,
$port
,
[
'type'
=>
self
::
PT_ASKINFO
]);
}
function
Info
(
string
&
$addr
=
NULL
)
{
if
(
!
(
$pk
=
self
::
Read
(
self
::
PT_SERVERINFO
)
))
return
FALSE
;
$this
->
fileneedednum
=
$pk
[
'fileneedednum'
];
$this
->
fileneeded
=
$pk
[
'fileneeded'
];
$version
=
$pk
[
'version'
];
$subversion
=
$pk
[
'subversion'
];
// SRB2Kart 1.0.4 and 1.10.0
if
((
$version
==
100
&&
$subversion
==
4
)
||
$version
==
110
)
{
self
::
Unkartvars
(
$t
,
$pk
);
}
else
{
copy_bool
(
$pk
[
'dedicated'
],
$pk
[
'isdedicated'
]);
$this
->
lotsofaddons
=
FALSE
;
}
$t
[
'version'
]
=
[
'major'
=>
$version
/
100
,
'minor'
=>
$version
%
100
,
'patch'
=>
$subversion
,
];
$t
[
'version'
][
'name'
]
=
self
::
Versionname
(
$pk
);
$t
[
'servername'
]
=
$pk
[
'servername'
];
$t
[
'players'
]
=
[
'count'
=>
$pk
[
'numberofplayer'
],
'max'
=>
$pk
[
'maxplayer'
],
];
$t
[
'gametype'
]
=
self
::
Gametype
(
$pk
);
copy_bool
(
$t
[
'mods'
],
$pk
[
'modifiedgame'
]);
copy_bool
(
$t
[
'cheats'
],
$pk
[
'cheatsenabled'
]);
$t
[
'level'
]
=
[
'seconds'
=>
$pk
[
'leveltime'
]
/
self
::
TICRATE
,
'title'
=>
self
::
Zonetitle
(
$pk
),
'md5sum'
=>
bin2hex
(
$pk
[
'mapmd5'
]),
];
$info
=
$t
;
unset
(
$t
);
$addr
=
$this
->
addr
;
if
(
!
(
$mpk
=
self
::
Read
(
self
::
PT_PLAYERINFO
,
FALSE
)
))
return
$info
;
$teams
=
[
self
::
GF_REDFLAG
=>
'Red'
,
self
::
GF_BLUEFLAG
=>
'Blue'
,
0
=>
'Playing'
,
255
=>
'Spectator'
,
];
$info
[
'players'
][
'list'
]
=
[];
for
(
$i
=
0
;
$i
<
32
;
++
$i
)
{
if
(
!
(
$pk
=
self
::
Unpk
(
$mpk
,
$i
)
))
break
;
if
(
$pk
[
'node'
]
<
255
)
{
$t
[
'name'
]
=
$pk
[
'name'
];
$team
=
$teams
[
$pk
[
'team'
]];
$t
[
'team'
]
=
(
(
$team
)
?
$team
:
'Unknown'
);
$t
[(
(
$version
==
100
||
$version
==
110
)
?
'rank'
:
'score'
)]
=
$pk
[
'score'
];
$t
[
'seconds'
]
=
$pk
[
'timeinserver'
];
$info
[
'players'
][
'list'
][]
=
$t
;
}
}
return
$info
;
}
function
Fileinfo
()
:
array
{
self
::
Unfileneeded
(
$fileinfo
,
$this
->
fileneedednum
,
$this
->
fileneeded
);
if
(
$this
->
lotsofaddons
)
{
$start
=
$this
->
fileneedednum
;
do
{
if
(
!
self
::
Send
([
'type'
=>
self
::
PT_TELLFILESNEEDED
,
'filesneedednum'
=>
$start
,
]))
break
;
if
(
!
(
$pk
=
self
::
Unpk
(
self
::
Read
(
self
::
PT_MOREFILESNEEDED
),
0
)
))
break
;
self
::
Unfileneeded
(
$fileinfo
,
$pk
[
'num'
],
$pk
[
'files'
]);
$start
+=
$pk
[
'num'
];
}
while
((
$pk
[
'more'
]
))
;
}
return
$fileinfo
;
}
function
Colorize
(
string
$s
)
:
string
{
// Probably not the pinnacle of performance.
for
(
$i
=
0x00
;
$i
<=
0x0F
;
++
$i
)
{
$codes
[
$i
]
=
chr
(
0x80
+
$i
);
$alts
[
$i
]
=
'^'
.
$i
;
}
// Remove anything that is not printable ASCII and sanitize!
$s
=
'<span>'
.
htmlentities
(
preg_replace
(
'/[\x00-\x19\x7F-\xFF]/'
,
''
,
str_replace
(
$codes
,
$alts
,
$s
)))
.
'</span>'
;
for
(
$i
=
0x00
;
$i
<=
0x0F
;
++
$i
)
{
$s
=
str_replace
(
$alts
[
$i
],
'</span><span style="color:'
.
$this
->
colors
[
$i
]
.
';">'
,
$s
);
}
return
$s
;
}
}
?>
This diff is collapsed.
Click to expand it.
util.php
0 → 100644
+
75
−
0
View file @
21cd4679
<?php
// vim: ts=3 sw=3
/*
Copyright 2019, James R.
All rights reserved.
Redistribution and use in source form, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of this project.
*/
/*
function cstrsize ( string $string [, int $offset = 0 [, int $limit ] ] ) : int
Return the length of a C string including the terminating byte, optionally
starting from offset and optionally up to limit.
function cstr ( string $string [, int $offset = 0 [, int $limit ] ] ) : string
Return a string that has been truncated not to include a terminating byte,
optionally starting from offset and optionally up to limit.
function copy_bool ( bool &$to, mixed $from ) : mixed
Copy a variable to a reference, casting it to bool. The casted variable is
also returned.
*/
function
cstrsize
(
string
$s
,
int
$l
=
0
,
int
$n
=
NULL
)
:
int
{
if
((
$len
=
strlen
(
$s
)
-
$l
)
<
0
)
return
0
;
// We can't substr outside of length.
if
(
isset
(
$n
)
&&
$n
<
$len
)
{
$s
=
substr
(
$s
,
$l
,
$n
);
$l
=
0
;
$len
=
$n
;
}
$n
=
strpos
(
$s
,
"
\0
"
,
$l
)
-
$l
;
return
(
(
$n
===
FALSE
)
?
$len
:
$n
+
1
);
}
function
cstr
(
string
$s
,
int
$l
=
0
,
int
$n
=
NULL
)
:
string
{
$n
=
cstrsize
(
$s
,
$l
,
$n
);
// Check that we haven't been truncated.
if
(
!
ord
(
$s
[
$l
+
(
$n
-
1
)]))
$n
--
;
return
substr
(
$s
,
$l
,
$n
);
}
function
copy_bool
(
bool
&
$to
=
NULL
,
$from
)
:
bool
{
$to
=
(
bool
)
$from
;
return
$to
;
}
?>
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment