Use avahi for network, share config over network, use unique for shared random
This commit is contained in:
parent
d1fe907235
commit
0c61d8a25a
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
bomber
|
||||
matcher
|
3
AUTHORS
3
AUTHORS
@ -2,3 +2,6 @@ David Ashley
|
||||
dashxdr@gmail.com
|
||||
http://www.xdr.com/dash
|
||||
http://www.linuxmotors.com
|
||||
|
||||
Stefan Bühler
|
||||
http://stbuehler.de/
|
||||
|
32
Makefile
32
Makefile
@ -1,37 +1,23 @@
|
||||
#DBG = -g
|
||||
CC = gcc
|
||||
CFLAGS = -O2 -Wall $(shell sdl-config --cflags) $(DBG)
|
||||
WARNFLAGS = -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wformat-security
|
||||
CFLAGS = -g -D_REENTRANT -O2 -Wall $(shell sdl-config --cflags) $(DBG) $(WARNFLAGS)
|
||||
|
||||
all: bomber matcher
|
||||
.PHONY: all clean
|
||||
all: bomber
|
||||
|
||||
bomber: bomber.o gfx.o sound.o matcher
|
||||
gcc -o bomber bomber.o gfx.o sound.o $(shell sdl-config --libs) $(DBG)
|
||||
bomber: bomber.o gfx.o sound.o announce.o matcher
|
||||
gcc -o bomber bomber.o gfx.o sound.o announce.o $(shell sdl-config --libs) -lavahi-common -lavahi-client $(DBG)
|
||||
|
||||
matcher: matcher.c
|
||||
|
||||
bomber.o: bomber.c bomber.h gfx.h
|
||||
bomber.o: bomber.c bomber.h gfx.h announce.h
|
||||
|
||||
gfx.o: gfx.c gfx.h bomber.h
|
||||
|
||||
sound.o: sound.c
|
||||
|
||||
announce.o: announce.c announce.h
|
||||
|
||||
clean:
|
||||
rm -f *.o matcher bomber
|
||||
|
||||
test: all
|
||||
./bomber
|
||||
|
||||
|
||||
|
||||
|
||||
WORK = /ram
|
||||
VER = 1.0.3
|
||||
DDIR = SDL_bomber-$(VER)
|
||||
|
||||
package: clean
|
||||
rm -rf $(WORK)/$(DDIR)
|
||||
mkdir $(WORK)/$(DDIR)
|
||||
cp *.c *.h Makefile* README INSTALL COPYING AUTHORS TODO $(WORK)/$(DDIR)
|
||||
cp -a data $(WORK)/$(DDIR)
|
||||
cp ChangeLog $(WORK)/$(DDIR)
|
||||
cd $(WORK) && tar czf $(DDIR).tgz $(DDIR)
|
||||
|
408
announce.c
Normal file
408
announce.c
Normal file
@ -0,0 +1,408 @@
|
||||
|
||||
#include "announce.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <avahi-client/client.h>
|
||||
#include <avahi-client/lookup.h>
|
||||
#include <avahi-client/publish.h>
|
||||
|
||||
#include <avahi-common/alternative.h>
|
||||
#include <avahi-common/error.h>
|
||||
#include <avahi-common/malloc.h>
|
||||
#include <avahi-common/thread-watch.h>
|
||||
|
||||
#define SERVICE_TYPE "_sdlbomber._udp"
|
||||
|
||||
static AvahiClient *client = NULL;
|
||||
static AvahiThreadedPoll *threaded_poll = NULL;
|
||||
static AvahiEntryGroup *group = NULL;
|
||||
|
||||
static char* name = NULL;
|
||||
|
||||
static uint16_t port = 0;
|
||||
static uint32_t unique = 0, version = 0;
|
||||
|
||||
static volatile int all_for_now;
|
||||
|
||||
gamelistentry gamelistentries[10];
|
||||
int gamelistsize = 0;
|
||||
|
||||
static void myerror(AvahiClient *c, const char *s) {
|
||||
fprintf(stderr, "Error in: %s\n (%s)", s, avahi_strerror(avahi_client_errno(client)));
|
||||
}
|
||||
|
||||
static void create_services(AvahiClient *c);
|
||||
|
||||
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
|
||||
group = g;
|
||||
|
||||
switch (state) {
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED :
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_COLLISION :
|
||||
{
|
||||
char *n = avahi_alternative_service_name(name);
|
||||
avahi_free(name);
|
||||
name = n;
|
||||
}
|
||||
|
||||
/* And recreate the services */
|
||||
avahi_entry_group_reset(group);
|
||||
create_services(avahi_entry_group_get_client(g));
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_FAILURE :
|
||||
fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
|
||||
avahi_threaded_poll_quit(threaded_poll);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_services(AvahiClient *c) {
|
||||
int ret;
|
||||
|
||||
if (!group) {
|
||||
if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) {
|
||||
myerror(c, "avahi_entry_group_new");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
again:
|
||||
if (avahi_entry_group_is_empty(group)) {
|
||||
char buf_unique[128], buf_version[128];
|
||||
snprintf(buf_unique, sizeof(buf_unique), "unique=%X", (unsigned int) unique);
|
||||
snprintf(buf_version, sizeof(buf_version), "version=%X", (unsigned int) version);
|
||||
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, SERVICE_TYPE, NULL, NULL, port, buf_unique, buf_version, NULL)) < 0) {
|
||||
|
||||
if (ret == AVAHI_ERR_COLLISION)
|
||||
goto collision;
|
||||
|
||||
fprintf(stderr, "Failed to add "SERVICE_TYPE": %s\n", avahi_strerror(ret));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((ret = avahi_entry_group_commit(group)) < 0) {
|
||||
fprintf(stderr, "Failed to commit entry group: %s\n", avahi_strerror(ret));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
collision:
|
||||
{
|
||||
char *n = avahi_alternative_service_name(name);
|
||||
avahi_free(name);
|
||||
name = n;
|
||||
}
|
||||
avahi_entry_group_reset(group);
|
||||
goto again;
|
||||
|
||||
fail:
|
||||
avahi_threaded_poll_quit(threaded_poll);
|
||||
}
|
||||
|
||||
static void client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {
|
||||
client = c;
|
||||
switch (state) {
|
||||
case AVAHI_CLIENT_S_RUNNING:
|
||||
if (port != 0) create_services(c);
|
||||
break;
|
||||
case AVAHI_CLIENT_FAILURE:
|
||||
myerror(c, "client failure");
|
||||
avahi_threaded_poll_quit(threaded_poll);
|
||||
break;
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
case AVAHI_CLIENT_S_REGISTERING:
|
||||
if (group) {
|
||||
avahi_entry_group_reset(group);
|
||||
}
|
||||
break;
|
||||
case AVAHI_CLIENT_CONNECTING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int registergame(char *playername, uint16_t p, uint32_t uniq, unsigned char v[4]) {
|
||||
if (name) avahi_free(name);
|
||||
name = avahi_strdup(playername);
|
||||
|
||||
port = p;
|
||||
unique = htonl(uniq);
|
||||
memcpy(&version, v, 4);
|
||||
version = htonl(version);
|
||||
|
||||
avahi_threaded_poll_lock(threaded_poll);
|
||||
create_services(client);
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void unregistergame() {
|
||||
port = 0;
|
||||
|
||||
avahi_threaded_poll_lock(threaded_poll);
|
||||
if (group) avahi_entry_group_reset(group);
|
||||
group = NULL;
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
}
|
||||
|
||||
static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
|
||||
const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address,
|
||||
uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata) {
|
||||
int i;
|
||||
uint32_t want_version = *(uint32_t*) userdata;
|
||||
assert(r);
|
||||
|
||||
if (protocol != AVAHI_PROTO_INET) goto done; /* ignore non IPv4 for now */
|
||||
if (gamelistsize >= GAMELIST_MAXSIZE) goto done;
|
||||
|
||||
/* Called whenever a service has been resolved successfully or timed out */
|
||||
|
||||
switch (event) {
|
||||
case AVAHI_RESOLVER_FAILURE:
|
||||
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
||||
break;
|
||||
|
||||
case AVAHI_RESOLVER_FOUND: {
|
||||
gamelistentry *ge;
|
||||
unsigned int uniq, version;
|
||||
int have_unique = 0, have_version = 0;
|
||||
AvahiStringList *psl;
|
||||
for (psl = txt ; psl ; psl = psl->next) {
|
||||
if (0 == strncmp("unique=", (const char*) psl->text, 7)) {
|
||||
sscanf((const char*) psl->text, "unique=%X", &uniq);
|
||||
have_unique = 1;
|
||||
} else if (0 == strncmp("version=", (const char*) psl->text, 8)) {
|
||||
sscanf((const char*) psl->text, "version=%X", &version);
|
||||
if (version != want_version) goto done; /* version mismatch */
|
||||
have_version = 1;
|
||||
}
|
||||
}
|
||||
if (!have_unique || !have_version) goto done;
|
||||
i = gamelistsize++;
|
||||
ge = &gamelistentries[i];
|
||||
memset(ge, 0, sizeof(*ge));
|
||||
ge->netname.sin_addr.s_addr = address->data.ipv4.address;
|
||||
ge->netname.sin_family = AF_INET;
|
||||
ge->netname.sin_port = port;
|
||||
ge->name = avahi_strdup(name);
|
||||
ge->unique = ntohl(uniq);
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
avahi_service_resolver_free(r);
|
||||
}
|
||||
|
||||
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
|
||||
const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) {
|
||||
|
||||
assert(b);
|
||||
|
||||
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
|
||||
|
||||
switch (event) {
|
||||
case AVAHI_BROWSER_FAILURE:
|
||||
|
||||
fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_client_errno(client)));
|
||||
avahi_threaded_poll_quit(threaded_poll);
|
||||
return;
|
||||
|
||||
case AVAHI_BROWSER_NEW:
|
||||
/* fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); */
|
||||
|
||||
/* We ignore the returned resolver object. In the callback
|
||||
function we free it. If the server is terminated before
|
||||
the callback function is called the server will free
|
||||
the resolver for us. */
|
||||
|
||||
if (!(avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, userdata)))
|
||||
fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(client)));
|
||||
|
||||
break;
|
||||
|
||||
case AVAHI_BROWSER_REMOVE:
|
||||
break;
|
||||
|
||||
case AVAHI_BROWSER_ALL_FOR_NOW:
|
||||
all_for_now = 1;
|
||||
break;
|
||||
case AVAHI_BROWSER_CACHE_EXHAUSTED:
|
||||
all_for_now = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void freefoundgames() {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < gamelistsize; i++) {
|
||||
avahi_free(gamelistentries[i].name);
|
||||
}
|
||||
gamelistsize = 0;
|
||||
}
|
||||
|
||||
int searchgames(unsigned char version[4]) {
|
||||
int i;
|
||||
AvahiServiceBrowser *sb = NULL;
|
||||
uint32_t gameversion;
|
||||
memcpy(&gameversion, version, 4);
|
||||
gameversion = htonl(gameversion);
|
||||
|
||||
freefoundgames();
|
||||
|
||||
avahi_threaded_poll_lock(threaded_poll);
|
||||
|
||||
all_for_now = 0;
|
||||
|
||||
if (NULL == (sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, SERVICE_TYPE, NULL, 0, browse_callback, &gameversion))) {
|
||||
fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
usleep(200000);
|
||||
|
||||
avahi_threaded_poll_lock(threaded_poll);
|
||||
|
||||
if (all_for_now) {
|
||||
avahi_service_browser_free(sb);
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
return 1;
|
||||
}
|
||||
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
}
|
||||
|
||||
avahi_threaded_poll_lock(threaded_poll);
|
||||
avahi_service_browser_free(sb);
|
||||
avahi_threaded_poll_unlock(threaded_poll);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int initannouncer() {
|
||||
if (!(threaded_poll = avahi_threaded_poll_new())) {
|
||||
fprintf(stderr, "avahi_threaded_poll_new failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(client = avahi_client_new(avahi_threaded_poll_get(threaded_poll), 0, client_callback, NULL, NULL))) {
|
||||
fprintf(stderr, "avahi_client_new failed\n");
|
||||
avahi_threaded_poll_free(threaded_poll);
|
||||
threaded_poll = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (avahi_threaded_poll_start(threaded_poll) < 0) {
|
||||
fprintf(stderr, "avahi_threaded_poll_start failed\n");
|
||||
avahi_client_free(client);
|
||||
avahi_threaded_poll_free(threaded_poll);
|
||||
client = NULL;
|
||||
threaded_poll = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void freeannouncer() {
|
||||
freefoundgames();
|
||||
|
||||
avahi_threaded_poll_stop(threaded_poll);
|
||||
if (client) avahi_client_free(client);
|
||||
if (threaded_poll) avahi_threaded_poll_free(threaded_poll);
|
||||
client = NULL;
|
||||
threaded_poll = NULL;
|
||||
|
||||
avahi_free(name);
|
||||
name = NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int openmatcher()
|
||||
{
|
||||
struct hostent *hostptr;
|
||||
if(matcheropened) return 1;
|
||||
hostptr=gethostbyname(mname);
|
||||
if(!hostptr)
|
||||
{
|
||||
hostptr=gethostbyaddr(mname,strlen(mname),AF_INET);
|
||||
if(!hostptr)
|
||||
return 0;
|
||||
}
|
||||
memset(&matchername,0,sizeof(matchername));
|
||||
matchername.sin_family=AF_INET;
|
||||
matchername.sin_port=htons(PORT);
|
||||
memcpy(&matchername.sin_addr,hostptr->h_addr,hostptr->h_length);
|
||||
matcheropened=1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int registergame()
|
||||
{
|
||||
long now;
|
||||
int size;
|
||||
long lastreg;
|
||||
|
||||
if(!openmatcher()) return 0;
|
||||
pulseoff();
|
||||
now=longtime();
|
||||
lastreg=now-1;
|
||||
while(longtime()-now<10)
|
||||
{
|
||||
if(longtime()-lastreg>=1)
|
||||
{
|
||||
lastreg=longtime();
|
||||
putmsg(&matchername,regpacket,REGISTERLEN);
|
||||
}
|
||||
size=getmsg(1000);
|
||||
if(size<REGISTERLEN+1) continue;
|
||||
if(mesg[0]!=PKT_ACK) continue;
|
||||
if(memcmp(regpacket,mesg+1,REGISTERLEN)) continue;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int unregistergame()
|
||||
{
|
||||
long now;
|
||||
int size;
|
||||
|
||||
|
||||
if(!openmatcher()) return 0;
|
||||
pulseoff();
|
||||
now=longtime();
|
||||
clearreg();
|
||||
while(longtime()-now<10)
|
||||
{
|
||||
putmsg(&matchername,regpacket,REGISTERLEN);
|
||||
size=getmsg(1000);
|
||||
if(size<REGISTERLEN+1) continue;
|
||||
if(mesg[0]!=PKT_ACK) continue;
|
||||
if(memcmp(regpacket,mesg+1,REGISTERLEN)) continue;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
#endif
|
25
announce.h
Normal file
25
announce.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef ANNOUNCE_H
|
||||
#define ANNOUNCE_H
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
typedef struct gamelistentry gamelistentry;
|
||||
struct gamelistentry {
|
||||
struct sockaddr_in netname;
|
||||
char *name;
|
||||
uint32_t unique;
|
||||
};
|
||||
|
||||
int registergame(char *playername, uint16_t port, uint32_t unique, unsigned char version[4]);
|
||||
void unregistergame();
|
||||
int searchgames(unsigned char version[4]);
|
||||
|
||||
int initannouncer();
|
||||
void freeannouncer();
|
||||
|
||||
#define GAMELIST_MAXSIZE 10
|
||||
|
||||
extern gamelistentry gamelistentries[GAMELIST_MAXSIZE];
|
||||
extern int gamelistsize;
|
||||
|
||||
#endif
|
2
bomber.h
2
bomber.h
@ -66,6 +66,7 @@ typedef struct player
|
||||
int flags;
|
||||
int abilities;
|
||||
int speed;
|
||||
int speedturtle_timeout;
|
||||
int bombsused;
|
||||
int bombsavailable;
|
||||
int flamelength;
|
||||
@ -164,6 +165,7 @@ typedef struct bonustile
|
||||
int px,py;
|
||||
int type;
|
||||
}bonustile;
|
||||
|
||||
#define TILE_NONE -1
|
||||
#define TILE_BOMB 5
|
||||
#define TILE_FLAME 2
|
||||
|
33
gfx.c
33
gfx.c
@ -29,7 +29,7 @@ void dumpgfx()
|
||||
{
|
||||
usedcolors = 0;
|
||||
}
|
||||
int bestmatch(int red,int green,int blue)
|
||||
static int bestmatch(int red,int green,int blue)
|
||||
{
|
||||
int i;
|
||||
int bestcolor,bestdelta=0;
|
||||
@ -61,7 +61,7 @@ int delta;
|
||||
return bestcolor;
|
||||
}
|
||||
|
||||
void updatemap(void)
|
||||
static void updatemap(void)
|
||||
{
|
||||
SDL_SetColors(thescreen, themap, 0, 256);
|
||||
}
|
||||
@ -117,7 +117,7 @@ int cnt;
|
||||
updatemap();
|
||||
}
|
||||
|
||||
uchar *compressfig(uchar *put,gfxset *gs,
|
||||
static uchar *compressfig(uchar *put,gfxset *gs,
|
||||
int sourcex,int sourcey,int sizex,int sizey)
|
||||
{
|
||||
int j,gswidth;
|
||||
@ -154,7 +154,7 @@ int dx,dy;
|
||||
*put++=0;
|
||||
return put;
|
||||
}
|
||||
void gfxfetchsingle(figure *fig,gfxset *gs,int sourcex,int sourcey,int sizex,int sizey)
|
||||
static void gfxfetchsingle(figure *fig,gfxset *gs,int sourcex,int sourcey,int sizex,int sizey)
|
||||
{
|
||||
uchar *p,*p2;
|
||||
int dx,dy;
|
||||
@ -401,15 +401,13 @@ void copyupxysize(int x,int y,int xsize,int ysize)
|
||||
}
|
||||
|
||||
|
||||
void set_color(int color, int red, int green, int blue)
|
||||
{
|
||||
themap[color].r=red;
|
||||
themap[color].g=green;
|
||||
themap[color].b=blue;
|
||||
// static void set_color(int color, int red, int green, int blue) {
|
||||
// themap[color].r=red;
|
||||
// themap[color].g=green;
|
||||
// themap[color].b=blue;
|
||||
// }
|
||||
|
||||
}
|
||||
void opengfx(int argc, char **argv)
|
||||
{
|
||||
void opengfx(int argc, char **argv) {
|
||||
unsigned long videoflags;
|
||||
|
||||
themap[0].r=0;
|
||||
@ -657,17 +655,16 @@ SDLK_LALT,MYALTL,
|
||||
SDLK_RALT,MYALTR,
|
||||
ENDMARK
|
||||
};
|
||||
int looklist(int code,int *list)
|
||||
{
|
||||
while(*list!=ENDMARK)
|
||||
static int looklist(int code,int *list)
|
||||
{
|
||||
while((unsigned int)*list!=ENDMARK) {
|
||||
if(*list==code)
|
||||
return list[1];
|
||||
list+=2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int mapkey(int code,int qual)
|
||||
static int mapkey(int code,int qual)
|
||||
{
|
||||
|
||||
if(qual & KMOD_SHIFT)
|
||||
@ -681,7 +678,7 @@ int mapkey(int code,int qual)
|
||||
|
||||
return code;
|
||||
}
|
||||
void markkey(int code,int status)
|
||||
static void markkey(int code,int status)
|
||||
{
|
||||
int i;
|
||||
int *ip;
|
||||
@ -818,7 +815,7 @@ int i,*ip,code;
|
||||
}
|
||||
*/
|
||||
|
||||
void drawrect(int x,int y,int xs,int ys,int c)
|
||||
static void drawrect(int x,int y,int xs,int ys,int c)
|
||||
{
|
||||
uchar *p;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user