Previous Thread
Next Thread
Print Thread
Page 1 of 2 1 2
Battlezone fun #116800 02/02/20 12:01 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
Hi guys,

I was always curious about Battlezone and I've been trying to figure out how it all works. My main goal is to find out where the 3d models for the tanks and objects are stored.

But first, I wanted to find the sin/cos table, since it's got to have one.

Sure enough, there's one at 5e77 to 5ef7.

A little bit of lua to visualize it: (we'll render it 3 times to make it brighter)


Code

function hex(a) return string.format("%x",a) end
mem=manager:machine().devices[":maincpu"].spaces["program"]

emu.pause()

for brightloop=1,3 do
xo=0 yo=200 xpos=xo
for wave=1,2 do
--middle to under
ibits=7 for i=0x5e77,0x5ef7,2 do val=mem:read_u16(i) x=math.floor(val/0x7fff*100) print(hex(i),hex(val),hex(x)) manager:machine().screens[":screen"]:draw_line(xpos,yo+x,xpos,yo+x,0xff000000 | (math.floor((ibits/11)*0xff)<<8)) xpos=xpos+1 end
--under to middle
for i=0x5ef7-2,0x5e77,-2 do val=mem:read_u16(i) x=math.floor(val/0x7fff*100) print(hex(i),hex(val),hex(x)) manager:machine().screens[":screen"]:draw_line(xpos,yo+x,xpos,yo+x,0xff000000 | (math.floor((ibits/11)*0xff)<<8)) xpos=xpos+1 end
-- middle to top
for i=0x5e77+2,0x5ef7,2 do val=mem:read_u16(i) x=math.floor(val/0x7fff*100) print(hex(i),hex(val),hex(x)) manager:machine().screens[":screen"]:draw_line(xpos,yo-x,xpos,yo-x,0xff000000 | (math.floor((ibits/11)*0xff)<<8)) xpos=xpos+1 end
--top to middle
for i=0x5ef7-2,0x5e77+2,-2 do val=mem:read_u16(i) x=math.floor(val/0x7fff*100) print(hex(i),hex(val),hex(x)) manager:machine().screens[":screen"]:draw_line(xpos,yo-x,xpos,yo-x,0xff000000 | (math.floor((ibits/11)*0xff)<<8)) xpos=xpos+1 end
print("wave"..wave.."done")
end -- wave
end --brightloop



[Linked Image from i.imgur.com]

A few more sine waves and I could be at the Outer Limits.

[Linked Image from i.imgur.com]

Just out of curiosity, I noticed that the vectors don't line up perfectly at the bottom on the self test screen.

A video that shows the self test screen close up on a real machine where the vectors don't line up exactly:

https://www.youtube.com/watch?v=YGMdVn0sN80&t=80s

I looked at the self test for a few of the other vector games and the one for quantum doesn't line up perfectly (I overlaid 2 of the pattern screenshots using gimp)

[Linked Image from i.imgur.com]

Re: Battlezone fun [Re: Golden Child] #116930 02/23/20 04:38 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374

I was looking for a battlezone disassembly and didn't find one, but did find this interesting thread:

https://forums.arcade-museum.com/archive/index.php/t-88714.html

which pointed me to the avg docs:

http://www.ionpool.net/arcade/atari_docs/avg.pdf
http://arcarc.xmission.com/Tech/neilw_xy.txt

The avg is pretty interesting.

Let's see if we can draw some vectors from the data in rom:

I was having trouble getting the moon to draw properly until I figured out that short vector numbers represent "one-half" of the actual value (in other words, multiply by 2). If you can read "half" below your eyes (or monitor) is better than mine.

[Linked Image from i.imgur.com]

[Linked Image from i.imgur.com]

the moon

[Linked Image from i.imgur.com]



Code

-- pause the emulator with p or emu.pause() or you won't see anything on screen
-- unpause with p or emu.unpause() to remove the drawing

function printt(a) for i,j in pairs(a) do print ("ITEM "..i,hex(j)) end end
function maskbits(a,b) local r = 0,i for i=a,b do r=r|(2^i) end return r end
function getbits(n,a,b) return (n & maskbits(a,b))>>a end
function hex(a) return string.format("%x",a) end
function iif(a,b,c) if a then return b else return c end end

mem=manager:machine().devices[":maincpu"].spaces["program"]

function cmdprocess(i) 
print("ADDRESS="..hex(i))
cmd=mem:read_u16(i) i=i+2 cmdbits=getbits(cmd,13,15) 
if (cmdbits<<1) == 0x4 then
xbits=getbits(cmd,0,4) if getbits(xbits,4,4)==1 then xbits=xbits-32 end  
ibits=getbits(cmd,5,7) 
ybits=getbits(cmd,8,12) if getbits(ybits,4,4)==1 then ybits=ybits-32 end 
print(hex(cmd),hex(cmdbits<<1),xbits,ybits,ibits) 
elseif (cmdbits<<1) == 0x0 then 
   io.write("MEMORY"..hex(i-2)..":")for k=i-2,i-2+3 do io.write(hex(mem:read_u8(k)).." ") end print()
   cmd=(cmd<<16) | mem:read_u16(i) i=i+2
  xbits=getbits(cmd,0,12) 
  if getbits(xbits,12,12)==1 then xbits=xbits-2^13 end   
  ibits=getbits(cmd,13,15) 
  ybits=getbits(cmd,16,16+8+5-1) 
  if getbits(ybits,12,12)==1 then ybits=ybits-2^13 end 
  print(hex(cmd),hex(cmdbits<<1).." x="..xbits.." y="..ybits.."  i="..ibits) 
elseif ((cmdbits<<1) == 0xe or ((cmdbits<<1) == 0xa)) then 
  jumpbits=getbits(cmd,0,12) print(hex(cmd),hex(cmdbits<<1).."  JUMP="..hex(jumpbits)) 
  jumpaddress=jumpbits*2+0x2000
  --beforejump = i i=jumpbits*2+0x2000
  if (cmdbits<<1)==0xa then print("ADD TO STACK"..hex(i)) table.insert(callstack,i) end
  beforejump=i
  i=jumpaddress
  print("JUMP ADDRESS="..hex(jumpaddress))
elseif ((cmdbits<<1) == 0xc) then
   print("RETURN FROM SUBROUTINE",hex(cmd),hex(cmdbits<<1))
   printt(callstack)
   if #callstack==0 then print("RETURN STACK EMPTY") 
   else i=table.remove(callstack) print ("RETURN STACK ADDRESS="..hex(i)) cmd=cmd<<8 end
elseif ((cmdbits<<1) == 0x70) then print("NEW SCALE ***************") 
else
  print(hex(cmd),hex(cmdbits<<1))
end
return i
end


-- draw the alphabet

xo=0 yo=100 xs=0.3 ys=xs stuckmax=1500 stuckcount=0 i=0x3282 callstack={} for count=1,40 do x=xo y=yo print(manager:machine().screens[":screen"]:draw_text(x,y-20,hex(i))) print ("COUNT="..count) x=xo y=yo cmd=0 beforejump=0 while  not(cmd == 0xc000 and #callstack == 0) and stuckcount < stuckmax do stuckcount=stuckcount + 1 i=cmdprocess(i)   if ((cmdbits<<1) == 0x4) or ((cmdbits<<1) == 0) then x1=x+xbits*iif((cmdbits<<1)==0,1,2)*xs y1=y-ybits*iif((cmdbits<<1)==0,1,2)*ys print(manager:machine().screens[":screen"]:draw_line(x,y,x1,y1,0xff000000 | (math.floor((ibits/11)*0xff)<<8))) x=x1 y=y1 end end  if beforejump ~= 0 then i=beforejump end xo=xo+60 if xo > 440 then xo=0 yo=yo+60 end end


-- draw the moon

xo=100 xs=2.0 ys=xs stuckmax=500 stuckcount=0 i=0x3058 callstack={} for count=1,1 do x=xo y=yo print(manager:machine().screens[":screen"]:draw_text(x,y-20,hex(i))) print ("COUNT="..count) x=xo y=yo cmd=0 beforejump=0 while  not(cmd == 0xc000 and #callstack == 0) and stuckcount < stuckmax do stuckcount=stuckcount + 1 i=cmdprocess(i)  if ((cmdbits<<1) == 0x4) or ((cmdbits<<1) == 0) then x1=x+xbits*iif((cmdbits<<1)==0,1,2)*xs y1=y-ybits*iif((cmdbits<<1)==0,1,2)*ys print(manager:machine().screens[":screen"]:draw_line(x,y,x1,y1,0xff000000 | (math.floor((ibits/11)*0xff)<<8))) x=x1 y=y1 end end  if beforejump ~= 0 then i=beforejump end xo=xo+20 if xo > 400 then xo=0 yo=yo+20 end end


Re: Battlezone fun [Re: Golden Child] #117021 03/12/20 05:52 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
Ok, to make sense of what the AVG is doing, let's write a little disassembler for the instructions because it's so hard to do it by hand. Not impossible, but really difficult.

So after writing some decoding routines, it's much easier to see what's going on.

Taking an attract mode screen, let's see what it decodes as:

[Linked Image from i.imgur.com]

Code
disavg(0x2000,300)disavg(0x2000,300)
DISAVG: 2000 for 300 instructions
2000: e401 CMD=e e401  0e  JUMP=401  JMPL 2802 character *
2002: 7100 CMD=6 SCAL 0100
2004: 8040 CMD=8 CNTR 
2006: 1e04 CMD=0  2006:04 1e 04 1e VCTR 1e041e04 0   x=-508  y=-508  i=0    (-508,-508)
200a: 6003 CMD=6 STAT 
200c: 8040 CMD=8 CNTR 
200e: 00c0 CMD=0  200e:c0 00 fc 01 VCTR 00c001fc 0   x= 508  y= 192  i=0    ( 508, 192)
2012: 6203 CMD=6 STAT 
2014: 8040 CMD=8 CNTR 
2016: 1f10 CMD=0  2016:10 1f 58 1f VCTR 1f101f58 0   x=-168  y=-240  i=0    (-168,-240)
201a: 7100 CMD=6 SCAL 0100
201c: aa2a CMD=a aa2a  0a  JUMP=a2a  JSRL 3454 character Character39  Char=c
201e: aa33 CMD=a aa33  0a  JUMP=a33  JSRL 3466 character Character40  Char=p
2020: a993 CMD=a a993  0a  JUMP=993  JSRL 3326 character Character_0  Char= 
2022: a993 CMD=a a993  0a  JUMP=993  JSRL 3326 character Character_0  Char= 
2024: a941 CMD=a a941  0a  JUMP=941  JSRL 3282 character Character11  Char=A
2026: a9af CMD=a a9af  0a  JUMP=9af  JSRL 335e character Character30  Char=T
2028: a941 CMD=a a941  0a  JUMP=941  JSRL 3282 character Character11  Char=A
202a: a9a2 CMD=a a9a2  0a  JUMP=9a2  JSRL 3344 character Character28  Char=R
202c: a974 CMD=a a974  0a  JUMP=974  JSRL 32e8 character Character19  Char=I
202e: a993 CMD=a a993  0a  JUMP=993  JSRL 3326 character Character_0  Char= 
2030: a9cf CMD=a a9cf  0a  JUMP=9cf  JSRL 339e character Character_2  Char=1
2032: a9f4 CMD=a a9f4  0a  JUMP=9f4  JSRL 33e8 character Character10  Char=9
2034: a9f2 CMD=a a9f2  0a  JUMP=9f2  JSRL 33e4 character Character_9  Char=8
2036: a98f CMD=a a98f  0a  JUMP=98f  JSRL 331e character Character_1  Char=0
2038: 7100 CMD=6 SCAL 0100
203a: 8040 CMD=8 CNTR 
203c: 0000 CMD=0  203c:00 00 a8 03 VCTR 000003a8 0   x= 936  y=  0  i=0    ( 936,  0)
2040: a800 CMD=a a800  0a  JUMP=800  JSRL 3000 character Item Left Horizontal Line
2042: a80b CMD=a a80b  0a  JUMP=80b  JSRL 3016 character Mountain_0
2044: a865 CMD=a a865  0a  JUMP=865  JSRL 30ca character Mountain_1
2046: a882 CMD=a a882  0a  JUMP=882  JSRL 3104 character Mountain_2
2048: 8040 CMD=8 CNTR 




Soooo much easier to see what's going on.

Code
-- drawvect() and disavg()
--
-- dofile("avg.lua")

function printthex(a) for i,j in pairs(a) do print ("ITEM "..i,hex(j)) end end
function printt(a) for i,j in pairs(a) do print ("ITEM "..i,j) end end
function maskbits(a,b) local r = 0,i for i=a,b do r=r|(2^i) end return r end
function getbits(n,a,b) return (n & maskbits(a,b))>>a end
function iif(a,b,c) if a then return b else return c end end
function hex(a,digits) digits = digits or 2 return string.format("%0"..digits.."x",a) end
function table16lookup(item,base) return mem:read_u16(base+item*2) end
function int(i) return math.floor(i) end

function avgjumpadr(i) jumpbits=getbits(i,0,12) jumpaddress=jumpbits*2+0x2000 return jumpaddress end

mem=manager:machine().devices[":maincpu"].spaces["program"]

charstr=" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ _cp"

function lookup_character(adr) local chartableentry,jumpbits,jumpaddress for i=1,charstr:len() do if avgjumpadr(mem:read_u16((i-1)*2+0x33f0))==adr then return charstr:sub(i,i) end end for i=0,7 do  if avgjumpadr(mem:read_u16(i*2+0x3006))==adr then return "Mountains"..i end end  for i=0,7 do  if avgjumpadr(mem:read_u16(i*2+0x36f8))==adr then return "ScreenCrack"..i end end for i=1,#drawitemtable do if adr==drawitemtable[i][1] then return "Item "..drawitemtable[i][2] end end return "*" end

function lookup_character(adr) 
for j=1,#drawtablestable do 
for i=1,drawtablestable[j][4] do 
if avgjumpadr(mem:read_u16((i-1)*2+drawtablestable[j][1]))==adr then return drawtablestable[j][3]..string.gsub(string.format("%2d",i-1)," ","_") ..iif(j==1,"  Char="..charstr:sub(i,i),"")
end end end
for i=1,#drawitemtable do if adr==drawitemtable[i][1] then return "Item "..drawitemtable[i][2] end end 
return "*" end

drawtablestable=
{
--address, tablename,         itemdescription, tablelength
{0x33f0, "CHARTABLE",        "Character",      charstr:len()}, 
{0x3006, "MOUNTAINTABLE",    "Mountain",       8},
{0x36f8 ,"SCREENCRACKTABLE", "ScreenCrack",    8}
}

drawitemtable=
{ 
{0x3058, "Mac Tonight"}, 
{0x3000, "Left Horizontal Line"},
{0x33de, "Downstroke"}, 
{0x33f0, "CHARTABLE"}, 
{0x3006, "MOUNTAINTABLE"},
{0x3444, "CIRCLE DIAMOND"},
{0x347a, "BRIGHT DOTS"},
{0x34cc, "TARGET SQUARE"},
{0x3500, "TARGET LOCK ON"},
{0x353c, "RADAR"},
{0x3568, "TANK ICON"},
{0x36f8, "SCREENCRACKTABLE"},
{0x370a, "SELF TEST GRID"},
}

for i=1,#drawtablestable do printt(drawtablestable[i]) end

function formatxyi(x,y,i) return string.format ("x=% 3d  y=% 3d  i=%d    (% 3d,% 3d)",x,y,i,x,y) end

function cmdprocess(i,addtostack) 
if addtostack==nil then addtostack=true end
if lookup_character(i) ~= "*" then print(string.rep("-",40)) print(hex(i,4)..": "..lookup_character(i)) end
io.write(hex(i,4)..": ") cmd=mem:read_u16(i) i=i+2 cmdbits=getbits(cmd,13,15) 
io.write(hex(cmd,4).." ".."CMD="..hex(cmdbits<<1,1).." ")
if (cmdbits<<1) == 0x4 then
  xbits=getbits(cmd,0,4) if getbits(xbits,4,4)==1 then xbits=xbits-32 end  
  ibits=getbits(cmd,5,7) 
  ybits=getbits(cmd,8,12) if getbits(ybits,4,4)==1 then ybits=ybits-32 end 
  print("SVEC "..hex(cmd),hex(cmdbits<<1).."   "..formatxyi(xbits,ybits,ibits)) 
elseif (cmdbits<<1) == 0x0 then 
  io.write(" "..hex(i-2)..":")for k=i-2,i-2+3 do io.write(hex(mem:read_u8(k)).." ") end
  cmd=(cmd<<16) | mem:read_u16(i) i=i+2
  xbits=getbits(cmd,0,12) 
  if getbits(xbits,12,12)==1 then xbits=xbits-2^13 end   
  ibits=getbits(cmd,13,15) 
  ybits=getbits(cmd,16,16+8+5-1) 
  if getbits(ybits,12,12)==1 then ybits=ybits-2^13 end 
  print("VCTR "..hex(cmd,8).." "..hex(cmdbits<<1,1).."   "..formatxyi(xbits,ybits,ibits)) 
elseif ((cmdbits<<1) == 0xe or ((cmdbits<<1) == 0xa)) then 
  jumpbits=getbits(cmd,0,12) io.write(hex(cmd).."  "..hex(cmdbits<<1).."  JUMP="..hex(jumpbits).."  ") 
  jumpaddress=jumpbits*2+0x2000
  --beforejump = i i=jumpbits*2+0x2000
  if (cmdbits<<1)==0xa then if addtostack then io.write("ADD TO STACK: "..hex(i).."  ") table.insert(callstack,i) end end
  beforejump=i
  i=jumpaddress
  print(iif((cmdbits<<1)==0xa,"JSRL ","JMPL ")..hex(jumpaddress).." character "..lookup_character(jumpaddress))
elseif ((cmdbits<<1) == 0xc) then
   print("RTSL")
   printt(callstack)
   if #callstack==0 then print("RETURN STACK EMPTY") rtsemptyflag=true
   else i=table.remove(callstack) print ("RETURN STACK ADDRESS="..hex(i)) cmd=cmd<<8 end
elseif (cmdbits<<1) == 0x6 then io.write (iif(getbits(cmd,12,12)==0,"STAT ","SCAL "))  if getbits(cmd,12,12)==1 then print( hex(getbits(cmd,0,10),4)) else print () end
elseif (cmdbits<<1) == 0x8 then print("CNTR ")
elseif (cmdbits<<1) == 0x2 then print("HALT ")  
else
  print(hex(cmd),hex(cmdbits<<1))
end
return i
end

function drawvect(adr,xo,yo,scale,textoffset) xo=xo or 150 yo=yo or 250 scale=scale or 0.4 textoffset=textoffset or -20 rtsemptyflag=false xs=scale ys=xs stuckmax=968 stuckcount=0 i=adr callstack={} for count=1,1 do x=xo y=yo print(manager:machine().screens[":screen"]:draw_text(x,y+textoffset,hex(i)..":"..lookup_character(i))) print ("COUNT="..count) x=xo y=yo cmd=0 beforejump=0  cmdbits=0 while  not(((cmdbits<<1) == 0xc) and rtsemptyflag) and stuckcount < stuckmax and not ((cmdbits<<1)==0x2) do stuckcount=stuckcount + 1 i=cmdprocess(i)  if ((cmdbits<<1) == 0x8) then x=xo y=yo end if ((cmdbits<<1) == 0x4) or ((cmdbits<<1) == 0) then x1=x+xbits*iif((cmdbits<<1)==0,1,2)*xs y1=y-ybits*iif((cmdbits<<1)==0,1,2)*ys manager:machine().screens[":screen"]:draw_line(x,y,x1,y1,0xff000000 | (math.floor((ibits/11)*0xff)<<8)) x=x1 y=y1 end end  if beforejump ~= 0 then i=beforejump end xo=xo+20  end end


function disavg(adr,count) print("DISAVG: "..hex(adr).." for "..count.." instructions") i=adr callstack={} for counter=1,count do callstack={} originali=i cmdprocess(i,false) if cmdbits==0x0 then i=originali+4 else i=originali + 2 end end end


function drawmountains() mtntable=0x3006 for i=0,7 do drawvect(avgjumpadr(table16lookup(i,mtntable)),15+270*(i%2),180+65*int(i/2),0.5,-60) end end

print(manager:machine():debugger():command("go")) emu.pause()

for i=1,3 do drawmountains() end
print(manager:machine():debugger():command("snap"))

disavg(0x3000,20)



And let's draw some mountains while we're at it 8-)

[Linked Image from i.imgur.com]

Re: Battlezone fun [Re: Golden Child] #117022 03/12/20 11:22 PM
Joined: Mar 2001
Posts: 16,473
R
R. Belmont Offline
Very Senior Member
Offline
Very Senior Member
R
Joined: Mar 2001
Posts: 16,473
That's really cool!

Re: Battlezone fun [Re: R. Belmont] #117026 03/14/20 04:26 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
Thanks, RB!

====================================

Now we've got an AVG decoder let's see if we can figure out where those vectors are coming from.

The attract screen has some objects, and the cube to the right of center that's just below the horizon line could be useful to study.

[Linked Image from i.imgur.com]

We'll pause mame with "p" and take a look at the AVG display list disassembly.

Through process of elimination, looking at the first VCTR after a CNTR, we home in on 0x20e4. There's 8 possibilities and this one is most likely due to it being to the right of center and below the horizon.

Code
[MAME]> disavg(0x2000,260)

20e4: 8040 CMD=8 CNTR 
20e6: 1ff6 CMD=0  20e6:f6 1f 87 00 VCTR 1ff60087 0   x= 135  y=-10  i=0    ( 135,-10)
20ea: 6430 CMD=6 STAT 
20ec: 0000 CMD=0  20ec:00 00 11 20 VCTR 00002011 0   x= 17  y=  0  i=1    ( 17,  0)
20f0: 0000 CMD=0  20f0:00 00 0d 20 VCTR 0000200d 0   x= 13  y=  0  i=1    ( 13,  0)
20f4: 0000 CMD=0  20f4:00 00 ef 3f VCTR 00003fef 0   x=-17  y=  0  i=1    (-17,  0)
20f8: 0000 CMD=0  20f8:00 00 f3 3f VCTR 00003ff3 0   x=-13  y=  0  i=1    (-13,  0)
20fc: 0009 CMD=0  20fc:09 00 00 20 VCTR 00092000 0   x=  0  y=  9  i=1    (  0,  9)
2100: 0000 CMD=0  2100:00 00 11 20 VCTR 00002011 0   x= 17  y=  0  i=1    ( 17,  0)
2104: 0000 CMD=0  2104:00 00 0d 20 VCTR 0000200d 0   x= 13  y=  0  i=1    ( 13,  0)
2108: 0000 CMD=0  2108:00 00 ef 3f VCTR 00003fef 0   x=-17  y=  0  i=1    (-17,  0)
210c: 0000 CMD=0  210c:00 00 f3 3f VCTR 00003ff3 0   x=-13  y=  0  i=1    (-13,  0)
2110: 0000 CMD=0  2110:00 00 11 00 VCTR 00000011 0   x= 17  y=  0  i=0    ( 17,  0)
2114: 1ff7 CMD=0  2114:f7 1f 00 20 VCTR 1ff72000 0   x=  0  y= -9  i=1    (  0, -9)
2118: 0000 CMD=0  2118:00 00 0d 00 VCTR 0000000d 0   x= 13  y=  0  i=0    ( 13,  0)
211c: 0009 CMD=0  211c:09 00 00 20 VCTR 00092000 0   x=  0  y=  9  i=1    (  0,  9)
2120: 0000 CMD=0  2120:00 00 ef 1f VCTR 00001fef 0   x=-17  y=  0  i=0    (-17,  0)
2124: 1ff7 CMD=0  2124:f7 1f 00 20 VCTR 1ff72000 0   x=  0  y= -9  i=1    (  0, -9)
2128: 8040 CMD=8 CNTR 


Notice that all of the y moves are either 9 or -9. After the y coord goes -10 at 20e6, then at 20fc it comes back up +9, then goes -9 at 2114, +9 at 211c, and -9 at 2124.

The y coordinate stays below the horizon at y=0, going back between y=-10 and y=-1.

So let's do a trace.

First I'll set a watchpoint on writes to 0x2002 since that's where the displaylist starts (the displaylist alternates between 0x2002 and 0x2802).

Code
wp 2002,1,w
Watchpoint 1 set


In the debugger I'll enter these commands (or do a "source" from a file)

This is a good trace command because it shows you the status of the A,X,Y,P registers along with the status flags:

Code
trace bzone.tracenoloop2002.txt,0,noloop,{tracelog "A=%02x X=%02x Y=%02x P=%02x NV__DIZC\n                    %1x%1x__%1x%1x%1x%1x\n",A,X,Y,P,((p&0x80)!=0),((p&0x40)!=0),((p&0x8)!=0),((p&0x4)!=0),((p&0x2)!=0),((p&0x1)!=0);}


Let's reformat the tracelog statement a little bit so it's easier to digest:
Code
tracelog 
"A=%02x X=%02x Y=%02x P=%02x NV__DIZC\n
                    %1x%1x__%1x%1x%1x%1x\n",
A,X,Y,P,
((p&0x80)!=0),((p&0x40)!=0),((p&0x8)!=0),((p&0x4)!=0),((p&0x2)!=0),((p&0x1)!=0);}

The noloop is a useful parameter because we want to see all of the instructions executed.

I like to instrument all of the READ and WRITE activity which helps to show the actual data moving around in the system so we'll set some watchpoints to do it.

Code
wp 0,8000,r,1,{tracelog "READ addr=%02x data=%02x\n",wpaddr,wpdata;g}
wp 0,8000,w,1,{tracelog "WRITE addr=%02x data=%02x\n",wpaddr,wpdata;g}



Once we have these in place, we'll issue a "g" to get things started.
Code
Stopped at watchpoint 1 writing 00 to 00002002 (PC=7A70)
[MAME]> 
Stopped at watchpoint 1 writing 00 to 00002002 (PC=7A70)
[MAME]> 


It won't actually stop when it hits watchpoint 1 since there's another watchpoint that issues a "g" so we'll let it run after it hits watchpoint 1 a couple of times and then stop it with the enter key and then "trace off".

So our cube begins drawing at the location 20e4, let's search in our trace for a write to that location.
Using less on our trace file "less -n bzone.tracenoloop2002.txt" (-n for line numbers)


Code
/WRITE addr=20E4

  50378 7A6E: ldy #$00
  50381 A=40 X=80 Y=00 P=36 NV__DIZC
  50382                     00__0110
  50383 7A70: sta ($02), y
  50388 WRITE addr=20E4 data=40           <<<<<<<<<<<<< WRITE TO 20E4

Scanning backwards a little bit we can see an LDA ($3b), y
which looks interesting...

Code
  50235 5C68: ldy #$00
  50240 5C6A: lda ($3b), y
  50242 READ addr=3B data=D7
  50243 READ addr=3C data=74
  50244 READ addr=74D7 data=03


So let's see if we can find where $3b gets written:

Search backwards with less:
Code
? WRITE addr=3B

  50183 50B6: lda $0270, x
  50190 50B9: jsr $5c5c
  50199 5C5C: asl a
  50209 5C5E: lda $7472, y
  50212 READ addr=7490 data=D7
  50214 A=D7 X=06 Y=1E P=B4 NV__DIZC
  50216 5C61: sta $3b
  50218 WRITE addr=3B data=D7            <<<< write to 3B
  50222 5C63: lda $7473, y
  50225 READ addr=7491 data=74
  50229 5C66: sta $3c
  50231 WRITE addr=3C data=74           <<<<< write to 3C


so there's a lookup table at 7472 and the offset is 1E, the lookup address is 74D7

and let's do a hexdump on it using a couple of lua functions to hexdump:

Code
mem=manager:machine().devices[":maincpu"].spaces["program"]

function hexdump(adr,len,peritem,perrow) peritem=peritem or 1 perrow=perrow or 8 print("HEXDUMP addr="..hex(adr,4).."  len="..hex(len).."    peritem="..peritem) local rowcount=0 for i=adr,adr+(len-1)*peritem,peritem do if rowcount%perrow ==0 then io.write(hex(i,4)..": ") end local memval=0 for n=0,peritem-1 do memval=memval|(mem:read_u8(i+n)<<((n)*8)) end io.write(hex(memval,2*peritem).." ") if ((rowcount)%perrow) == (perrow-1) then print() end rowcount=rowcount+1 end print() end
function hexdump16(adr,len,perrow) hexdump(adr,len,2,perrow) end

Code
[MAME]> hexdump16(0x7472,16)
HEXDUMP16 addr=7472  len=10
7472: 74cb 74d7 74e9 7519 7525 7525 7525 7525 
7482: 7525 7525 7525 7525 74cb 752d 753b 74d7    <<< 74d7 at offset 1E

and let's look at 74d7
Code
[MAME]> hexdump(0x74d7,18)
HEXDUMP addr=74d7  len=12
74d7: 03 a1 0c 14 1c 04 24 2c 
74df: 34 3c 24 2a 0c 12 34 3a 
74e7: 1c ff 


What does this table do? I figured out what it does, it is the vertex table.

We'll analyze the vertex table later, but first I want to find the actual point data.

So we'll leave that for the next installment.

Re: Battlezone fun [Re: Golden Child] #117028 03/14/20 06:02 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374

Finding the Points Table
========================

So let's look a little further back in the trace, it looks like there's some math being done with the mathbox. There's writes to $1871 which is a mathbox address, and it's accessing data at 3C88, using LDA ($3b), y where 3b=3C5A and y=2e
Code
  49839 5DC1: lda ($3b), y
  49840 READ addr=5DC2 data=3B
  49841 READ addr=3B data=5A
  49842 READ addr=3C data=3C
  49843 READ addr=3C88 data=FD
  49845 A=FD X=1C Y=2E P=B4 NV__DIZC
  49847 5DC3: sta $1871


so let's scan backwards for where 3b gets set:

and that leads us to this table lookup at $388e:

Code
?WRITE addr=3B

  46690 5D76: lda $0270, x                 <<<<< loads the object # from $0270, x
  46693 READ addr=276 data=0F              <<<< number is 0xF
  46695 A=0F X=06 Y=00 P=34 NV__DIZC
  46697 5D79: asl a                        <<< multiply by 2
  46702 5D7A: tay                          <<<< transfer into y
  46705 A=1E X=06 Y=1E P=34 NV__DIZC
  46707 5D7B: lda $388e, y                 <<<<< lookup table at 388e
  46710 READ addr=38AC data=5A             <<<< get the low byte
  46712 A=5A X=06 Y=1E P=34 NV__DIZC
  46714 5D7E: sta $3b
  46716 WRITE addr=3B data=5A              <<<<< 3b gets written here
  46718 A=5A X=06 Y=1E P=34 NV__DIZC
  46720 5D80: lda $388f, y                 <<<<< now get the high byte
  46723 READ addr=38AD data=3C
  46727 5D83: sta $3c
  46729 WRITE addr=3C data=3C               <<<<< 3c gets written here
  46731 A=3C X=06 Y=1E P=34 NV__DIZC           full address is $3c5a

Taking a hexdump of the 0x388e table:

Code
[MAME]> hexdump16(0x388e,15)
HEXDUMP16 addr=388e  len=0f
388e: 38e6 3905 3955 3936 3a55 3a30 3a0b 39e6 
389e: 3a7a 3a9f 3ac4 3ae9 3c3b 3b0e 3b3f 3c5a   <<<<<<<< 3c5a at offset 1E

and the entry for item 0xf is $3c5a so let's have a look:
Code
[MAME]> hexdump(0x3c5a,48)
HEXDUMP addr=3c5a  len=30
3c5a: 31 80 fd 80 fd c0 fe 80 
3c62: fd 80 02 c0 fe 80 02 80 
3c6a: 02 c0 fe 80 02 80 fd c0 
3c72: fe 80 fd 80 fd d8 ff 80 
3c7a: fd 80 02 d8 ff 80 02 80 
3c82: 02 d8 ff 80 02 80 fd d8 


The first number must be the # of bytes in the point table plus 1 (since it includes the first byte), 0x31=49.

So 48 bytes of points, each point is 2 bytes, 3 points for (x,y,z) so there's 48/2/3=8 points.
Code
[MAME]> hexdump16(0x3c5a+1,8*3,3)
HEXDUMP16 addr=3c5b  len=18
3c5b: fd80 fd80 fec0 
3c61: fd80 0280 fec0 
3c67: 0280 0280 fec0 
3c6d: 0280 fd80 fec0 
3c73: fd80 fd80 ffd8 
3c79: fd80 0280 ffd8 
3c7f: 0280 0280 ffd8 
3c85: 0280 fd80 ffd8 

Hex is hard to decode so let's look at it in decimal:
Code
[MAME]> a=mem:read_u8(0x3c5a) 
[MAME]> print(a)
49

[MAME]> for i=0x3c5a+1,0x3c5a+1+a-1-1,6 do  x=mem:read_i16(i) y=mem:read_i16(i+2) z=mem:read_i16(i+4) print(hex(i,4),"("..int(x)..","..int(y)..","..int(z)..")") end
3c5b	(-640,-640,-320)
3c61	(-640,640,-320)
3c67	(640,640,-320)
3c6d	(640,-640,-320)
3c73	(-640,-640,-40)
3c79	(-640,640,-40)
3c7f	(640,640,-40)
3c85	(640,-640,-40)


They all seem to be multiples of 40, so let's divide by 40 to make the numbers a little simpler for us to understand.

Code
[MAME]> for i=0x3c5a+1,0x3c5a+1+a-1-1,6 do  x=mem:read_i16(i) y=mem:read_i16(i+2) z=mem:read_i16(i+4) print(hex(i,4),"("..int(x/40)..","..int(y/40)..","..int(z/40)..")") end
3c5b	(-16,-16,-8)
3c61	(-16,16,-8)
3c67	(16,16,-8)
3c6d	(16,-16,-8)
3c73	(-16,-16,-1)
3c79	(-16,16,-1)
3c7f	(16,16,-1)
3c85	(16,-16,-1)
[MAME]> 


If you draw the (x,y) components you get a square, and there's one square at z=-8 and another at z=-1 so definitely a cube, and it's the cube we're looking for.

So now let's see if we can change a point and see it visibly change on screen:

We'll open up a memory viewer in the debugger and go to our table at 0x3c5b. Note that we are in region :maincpu as the default region won't let you change the memory.



[Linked Image from i.imgur.com]

-> change 0x3c77 from 0xffd8 to 0 -> (changing the z coordinate from -40 to 0)

[Linked Image from i.imgur.com]

and you can see the vertex move right up to the horizon line.

[Linked Image from i.imgur.com]
[Linked Image from i.imgur.com]

Re: Battlezone fun [Re: Golden Child] #117030 03/14/20 09:26 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
Finding the Vertex Table
===========================

Okay, so now we've got a point table at $388e, which points to a set of points. The length of the point table comes first (size of table in bytes including length byte) followed by the 16 bit point triples, each triple entry being 6 bytes.

Now the vertex array lookup table is at $7472, which points to a set of vertexes. This vertex list is terminated with an FF byte.

So what's the format of this vertex table?
We lookup an address for our vertex table at $7472, put it into $3b and then start getting values from ($3b),y:
Code
  50209 5C5E: lda $7472, y
  50216 5C61: sta $3b
  50222 5C63: lda $7473, y
  50229 5C66: sta $3c
  50235 5C68: ldy #$00
  50240 5C6A: lda ($3b), y
  ...
  50264 5C72: and #$f8
  ...
  50280 5C77: and #$07


there's an and #$f8 which is bits 11111000
and an and #$07 which is bits 00000111

These low 3 bits get multiplied by 2 and used as an offset in a lookup table at $5c84. The address of the routine gets pushed on the stack and then an rts jumps to the routine address + 1.
Code
  50295 5C7B: lda $5c85, x
  50302 5C7E: pha
  50308 5C7F: lda $5c84, x
  50315 5C82: pha
  50321 5C83: rts


The high 5 bits are the vertex number or a parameter for the routine in the lookup table.

The lookup table at $5c84:
Code
[MAME]> hexdump16(0x5c84,7,1)
HEXDUMP16 addr=5c84  len=07
5c84: 5c91   (address is +1, so $5c92  00)
5c86: 5c9e   (address is +1, so $5c9f  01)
5c88: 5ce0   (address is +1, so $5ce1  02)
5c8a: 5cd3   (address is +1, so $5cd4  03)
5c8c: 5ce4   (address is +1, so $5ce5  04)
5c8e: 5cec   (address is +1, so $5ced  05)
5c90: 5d0d   (address is +1, so $5d0e  06)
5c92: (first routine in jump table)


Let's try to decode the data:
Code
[MAME]> hexdump(0x3c5a,16)
HEXDUMP addr=3c5a  len=10
3c5a: 31 80 fd 80 fd c0 fe 80 
3c62: fd 80 02 c0 fe 80 02 80 
3c6a: 02 

stuckcount=0 i=0x74d7  while mem:read_u8(i)~=0xff do m=mem:read_u8(i) print(hex(i,4)..":"..hex(m).."   "..hex(getbits(m,3,7)).."  "..hex(getbits(m,0,2))) i=i+1 stuckcount=stuckcount+1 if stuckcount>50 then break end end print(hex(i,4)..":"..hex(mem:read_u8(i)))

       param   routine
    (high 5)   (low 3 bits)
           |   | 
74d7:03   00  03
74d8:a1   14  01
74d9:0c   01  04
74da:14   02  04
74db:1c   03  04
74dc:04   00  04
74dd:24   04  04
74de:2c   05  04
74df:34   06  04
74e0:3c   07  04
74e1:24   04  04
74e2:2a   05  02
74e3:0c   01  04
74e4:12   02  02
74e5:34   06  04
74e6:3a   07  02
74e7:1c   03  04
74e8:ff


In the last column we have the routine that gets called, a number from 0 to 5.

03 starts the object, moves to a new location (MoveToStart)
01 sets the intensity
04 continues the vector (DrawTo)
02 ends the current line, then moves to a new location (MoveTo)

So if we put our generated AVG commands next to the vertex table decode, it's easy to see the pattern (you may need to widen the window or scroll to see all of it):

All DrawTos have (i=1)
All MoveTos have (i=0)
Code
20e4: 8040 CMD=8 CNTR									
20e6: 1ff6 CMD=0  20e6:f6 1f 87 00 VCTR 1ff60087 0   x= 135  y=-10  i=0	  ( 135,-10)	74d7:03	  00  03 Start New Object (point 0)
20ea: 6430 CMD=6 STAT									74d8:a1	  14  01 Stat (set brightness)
20ec: 0000 CMD=0  20ec:00 00 11 20 VCTR 00002011 0   x= 17  y=	0  i=1	 ( 17,	 0)	74d9:0c	  01  04 DrawTo (point 1)
20f0: 0000 CMD=0  20f0:00 00 0d 20 VCTR 0000200d 0   x= 13  y=	0  i=1	 ( 13,	 0)	74da:14	  02  04 DrawTo (point 2)
20f4: 0000 CMD=0  20f4:00 00 ef 3f VCTR 00003fef 0   x=-17  y=	0  i=1	 (-17,	 0)	74db:1c	  03  04 DrawTo (point 3)
20f8: 0000 CMD=0  20f8:00 00 f3 3f VCTR 00003ff3 0   x=-13  y=	0  i=1	 (-13,	 0)	74dc:04	  00  04 DrawTo (point 0)
20fc: 0009 CMD=0  20fc:09 00 00 20 VCTR 00092000 0   x=	 0  y=	9  i=1	 (  0,	 9)	74dd:24	  04  04 DrawTo (point 4)
2100: 0000 CMD=0  2100:00 00 11 20 VCTR 00002011 0   x= 17  y=	0  i=1	 ( 17,	 0)	74de:2c	  05  04 DrawTo (point 5)
2104: 0000 CMD=0  2104:00 00 0d 20 VCTR 0000200d 0   x= 13  y=	0  i=1	 ( 13,	 0)	74df:34	  06  04 DrawTo (point 6)
2108: 0000 CMD=0  2108:00 00 ef 3f VCTR 00003fef 0   x=-17  y=	0  i=1	 (-17,	 0)	74e0:3c	  07  04 DrawTo (point 7)
210c: 0000 CMD=0  210c:00 00 f3 3f VCTR 00003ff3 0   x=-13  y=	0  i=1	 (-13,	 0)	74e1:24	  04  04 DrawTo (point 4)
2110: 0000 CMD=0  2110:00 00 11 00 VCTR 00000011 0   x= 17  y=	0  i=0	 ( 17,	 0)	74e2:2a	  05  02 MoveTo (point 5)
2114: 1ff7 CMD=0  2114:f7 1f 00 20 VCTR 1ff72000 0   x=	 0  y= -9  i=1	 (  0, -9)	74e3:0c	  01  04 DrawTo (point 1)
2118: 0000 CMD=0  2118:00 00 0d 00 VCTR 0000000d 0   x= 13  y=	0  i=0	 ( 13,	 0)	74e4:12	  02  02 MoveTo (point 2)
211c: 0009 CMD=0  211c:09 00 00 20 VCTR 00092000 0   x=	 0  y=	9  i=1	 (  0,	 9)	74e5:34	  06  04 DrawTo (point 6)
2120: 0000 CMD=0  2120:00 00 ef 1f VCTR 00001fef 0   x=-17  y=	0  i=0	 (-17,	 0)	74e6:3a	  07  02 MoveTo (point 7)
2124: 1ff7 CMD=0  2124:f7 1f 00 20 VCTR 1ff72000 0   x=	 0  y= -9  i=1	 (  0, -9)	74e7:1c	  03  04 DrawTo (point 3)
2128: 8040 CMD=8 CNTR									



So let's write some routines to print out the vertex table and points table

Code
mem=manager:machine().devices[":maincpu"].spaces["program"]
mem2=manager:machine():memory().regions[":maincpu"]  -- alternate way to access memory
function hex(a,digits) digits = digits or 2 return string.format("%0"..digits.."x",a) end
function int(i) return math.floor(i) end
function tablelookup16(item,base) return mem:read_u16(base+item*2) end
function maskbits(a,b) local r = 0,i for i=a,b do r=r|(2^i) end return r end
function getbits(n,a,b) return (n & maskbits(a,b))>>a end
function iif(a,b,c) if a then return b else return c end end

function printvertextable(i) stuckcount=0 while mem:read_u8(i)~=0xff do m=mem:read_u8(i) print(hex(i,4)..":"..hex(m).."   "..hex(getbits(m,3,7)).."  "..hex(getbits(m,0,2))) i=i+1 stuckcount=stuckcount+1 if stuckcount>100 then print("STUCK LOOP="..stuckcount)  break end end print(hex(i,4)..":"..hex(mem:read_u8(i))) end

function printpointstable(adr) a=mem:read_u8(adr) local count=0 for i=adr+1,adr+1+a-1-1,6 do  x=mem:read_i16(i) y=mem:read_i16(i+2) z=mem:read_i16(i+4) print(hex(i,4).."  vertex "..count.." ("..int(x)..","..int(y)..","..int(z)..")") count=count+1 end end

function printline() print(string.rep("-",40)) end

battlezoneobjecttable={
"pyramid",
"cube",
"enemy tank",
"shot",
"rear tread 1",
"rear tread 2",
"rear tread 3",
"rear tread 4",
"front tread 1",
"front tread 2",
"front tread 3",
"front tread 4",
"pyramid wide",
"radar1",
"dot",
"short cube",
"fragment 1",
"fragment 2",
"fragment 3",
"fragment 4",
"fragment 5",
"fragment 6",
"missile",
"BA",
"fragment 7",
"fragment 8",
"fragment 9",
"fragment 10",
"fragment 11",
"fragment 12",
"TTLE",
"ZONE",
"saucer",
"supertank"
}

function printobject(obj) 
printline() print("OBJECT "..obj.." "..battlezoneobjecttable[obj+1])  printline() 
printpointstable(tablelookup16(obj,0x388e)) 
printvertextable(tablelookup16(obj,0x7472)) 
end


Code
printobject(0xf)

----------------------------------------
OBJECT 15 short cube
----------------------------------------
3c5b  vertex 0 (-640,-640,-320)
3c61  vertex 1 (-640,640,-320)
3c67  vertex 2 (640,640,-320)
3c6d  vertex 3 (640,-640,-320)
3c73  vertex 4 (-640,-640,-40)
3c79  vertex 5 (-640,640,-40)
3c7f  vertex 6 (640,640,-40)
3c85  vertex 7 (640,-640,-40)
74d7:03   00  03
74d8:a1   14  01
74d9:0c   01  04
74da:14   02  04
74db:1c   03  04
74dc:04   00  04
74dd:24   04  04
74de:2c   05  04
74df:34   06  04
74e0:3c   07  04
74e1:24   04  04
74e2:2a   05  02
74e3:0c   01  04
74e4:12   02  02
74e5:34   06  04
74e6:3a   07  02
74e7:1c   03  04
74e8:ff


for i=0,33 do printobject(i) end

Re: Battlezone fun [Re: Golden Child] #117032 03/15/20 03:28 AM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
Once we know the points and the vertexes, let's see if we can draw the 3d objects.

[Linked Image from i.imgur.com]
[Linked Image from i.imgur.com]

Code
--
-- drawbz.lua
--   dofile("drawbz.lua")  or  -autoboot_script drawbz.lua
--

function int(i) return math.floor(i) end

mem=manager:machine().devices[":maincpu"].spaces["program"]
mem2=manager:machine():memory().regions[":maincpu"]  -- alternate way to access memory
function hex(a,digits) digits = digits or 2 return string.format("%0"..digits.."x",a) end
function int(i) return math.floor(i) end
function tablelookup16(item,base) return mem:read_u16(base+item*2) end
function maskbits(a,b) local r = 0,i for i=a,b do r=r|(2^i) end return r end
function getbits(n,a,b) return (n & maskbits(a,b))>>a end
function iif(a,b,c) if a then return b else return c end end
function printt(a) for i,j in pairs(a) do print ("ITEM "..i,j) end end

function makevertextable(i) local v local v1 local vertextable={} stuckcount=0 printt(vertextable) while mem:read_u8(i)~=0xff do m=mem:read_u8(i) param=getbits(m,3,7) routine=getbits(m,0,2)  if routine==0x3 or routine==0x2 then v=param elseif routine == 0x4 or routine == 0x5 then v1=v v=param vertextable[#vertextable+1]={v1,v} end  -- 0x5 for the dot object
--print(hex(i,4)..":"..hex(m).."   "..hex(getbits(m,3,7)).."  "..hex(getbits(m,0,2))) 
i=i+1 stuckcount=stuckcount+1 if stuckcount>50 then break end end 
--print(hex(i,4)..":"..hex(mem:read_u8(i))) 
return vertextable end

function makepointstable(adr) local pointstable={} a=mem:read_u8(adr)  for i=adr+1,adr+1+a-1-1,6 do  x=mem:read_i16(i) y=mem:read_i16(i+2) z=mem:read_i16(i+4) 
--print(hex(i,4),"("..int(x)..","..int(y)..","..int(z)..")") 
pointstable[#pointstable+1]={int(x),int(y),int(z)} end return pointstable end

function printpoint(j) for k=1,#j do io.write(j[k]..iif(k<#j,", ","")) end end
function printvt(v) for i,j in pairs(v) do io.write(i..": ") for k=1,#j do io.write(j[k]..iif(k<#j,", ","")) end print() end end
function printvt(v) for i,j in pairs(v) do io.write(i..": ") printpoint(j) end print() end


function rotatexyz(x,y,z,a,axis) if axis=="x" then y,z=rotatexy(y,z,a) elseif axis=="y" then x,z=rotatexy(x,z,a) else x,y=rotatexy(x,y,a) end return x,y,z end
function rotatexy(x,y,a) local x1=x*math.cos(a)-y*math.sin(a) local y1=y*math.cos(a)+x*math.sin(a) return x1,y1 end
function translatexyz(x,y,z,x1,y1,z1) return x+x1,y+y1,z+z1 end
function scalexyz(x,y,z,sx,sy,sz) return x*sx,y*sy,z*sz end
function projectpoint(x,y,z,d) return project(x,z,d),project(y,z,d) end
function project(x,z,d) return x/z*d end

ztrans=2550 xo=200 yo=200 d=400

function drawobject(obj,rotx,roty,rotz,s)
s=s or 1.0
rotx=rotx or 0.0
roty=roty or 0.0
rotz=rotz or 0.0
pt=makepointstable(tablelookup16(obj,0x388e)) vt=makevertextable(tablelookup16(obj,0x7472))
rotx=math.pi*rotx/180.0
roty=math.pi*roty/180.0
rotz=math.pi*rotz/180.0
--print("rot",rotx,roty,rotz)
for a,b in pairs(vt) do 
--io.write(a..":  ("..b[1]..", "..b[2]..")   ")  printpoint(pt[b[1]+1]) io.write("  ") printpoint(pt[b[2]+1]) print()
  pt1=pt[b[1]+1] pt2=pt[b[2]+1] x1,y1,z1=pt1[1],pt1[2],pt1[3] x2,y2,z2=pt2[1],pt2[2],pt2[3]  
x1,y1,z1=scalexyz(x1,y1,z1,s,s,s)
x2,y2,z2=scalexyz(x2,y2,z2,s,s,s)
--print(x1,y1,z1) print(x2,y2,z2)
x1,y1,z1=rotatexyz(x1,y1,z1,rotx,"x")
x1,y1,z1=rotatexyz(x1,y1,z1,roty,"y")
x1,y1,z1=rotatexyz(x1,y1,z1,rotz,"z")
x2,y2,z2=rotatexyz(x2,y2,z2,rotx,"x")
x2,y2,z2=rotatexyz(x2,y2,z2,roty,"y")
x2,y2,z2=rotatexyz(x2,y2,z2,rotz,"z")
--print("rotatexyz")
--print(x1,y1,z1) print(x2,y2,z2)
x1,y1,z1=translatexyz(x1,y1,z1,0,0,ztrans*iif(obj==23 or obj==30 or obj==31,3.0,1.0))
x2,y2,z2=translatexyz(x2,y2,z2,0,0,ztrans*iif(obj==23 or obj==30 or obj==31,3.0,1.0)) 
x1,y1=projectpoint(x1,y1,z1,d) 
x2,y2=projectpoint(x2,y2,z2,d) 
--print("project",x1,y1,x2,y2) 
ibits=11 manager:machine().screens[":screen"]:draw_line(x1+xo,y1+yo,x2+xo,y2+yo,0xff0000ff | (math.floor((ibits/11)*0xff)<<8)| (math.floor((ibits/11)*0xff)<<16)) 
end
manager:machine().screens[":screen"]:draw_text(xo-30,yo+24,obj.." "..battlezoneobjecttable[obj+1],0xff0000ff | (math.floor((ibits/11)*0xff)<<8)| (math.floor((ibits/11)*0xff)<<16))
end



battlezoneobjecttable={
"pyramid",
"cube",
"enemy tank",
"shot",
"rear tread 1",
"rear tread 2",
"rear tread 3",
"rear tread 4",
"front tread 1",
"front tread 2",
"front tread 3",
"front tread 4",
"pyramid wide",
"radar1",
"dot",
"short cube",
"fragment 1",
"fragment 2",
"fragment 3",
"fragment 4",
"fragment 5",
"fragment 6",
"missile",
"BA",
"fragment 7",
"fragment 8",
"fragment 9",
"fragment 10",
"fragment 11",
"fragment 12",
"TTLE",
"ZONE",
"saucer",
"supertank"
}

--ztrans=2550 xo=200 yo=200 d=400
--drawobject(2,45,5,90)

for obj=0,33 do ztrans=19550 xo=50+85*(obj%6) yo=50+60*int(obj/6) d=600 drawobject(obj,0,0,0, 2.0) end -- overhead
for obj=0,33 do ztrans=19550 xo=50+85*(obj%6) yo=50+60*int(obj/6) d=600 drawobject(obj,90,0,0,2.0) end -- sideways
for obj=0,33 do ztrans=19550 xo=50+85*(obj%6) yo=50+60*int(obj/6) d=600 drawobject(obj,90,90,0,2.0) end -- at you

function drawbz() 
counter=counter or 0
counter=counter+1
rotx=rotx or 0
roty=roty or 0
rotz=rotz or 0
spinmode = spinmode or 0

inp = manager:machine():input() if inp:code_pressed(inp:code_from_token("KEYCODE_SPACE")) then print("X") end
inp = manager:machine():input() if inp:code_pressed(inp:code_from_token("KEYCODE_C")) then dispatch_list={} end

if spinmode == 0 then 
if int(counter/360)%3 == 0 then 
  rotx=rotx+1
elseif int(counter/360)%3 == 1 then
  roty=roty+1
elseif int(counter/360)%3 == 2 then
  rotz=rotz+1
end
end

inp = manager:machine():input() 

if inp:code_pressed(inp:code_from_token("KEYCODE_DOWN")) or
   inp:code_pressed(inp:code_from_token("KEYCODE_UP")) or
   inp:code_pressed(inp:code_from_token("KEYCODE_LEFT")) or
   inp:code_pressed(inp:code_from_token("KEYCODE_RIGHT")) or
   inp:code_pressed(inp:code_from_token("KEYCODE_Z")) or
   inp:code_pressed(inp:code_from_token("KEYCODE_X")) then spinmode = 1 
end

if spinmode == 1 then
if     inp:code_pressed(inp:code_from_token("KEYCODE_DOWN")) then rotx=rotx+1 
elseif inp:code_pressed(inp:code_from_token("KEYCODE_UP"))   then rotx=rotx-1 end
if     inp:code_pressed(inp:code_from_token("KEYCODE_LEFT"))  then rotz=rotz+1 
elseif inp:code_pressed(inp:code_from_token("KEYCODE_RIGHT")) then rotz=rotz-1 end
if     inp:code_pressed(inp:code_from_token("KEYCODE_Z")) then roty=roty+1 
elseif inp:code_pressed(inp:code_from_token("KEYCODE_X")) then roty=roty-1 end
end

delaykey=delaykey or 0

if inp:code_pressed(inp:code_from_token("KEYCODE_S")) and delaykey==0 then spinmode=1-spinmode delaykey=10 end 
if delaykey>0 then delaykey=delaykey - 1 end

for obj=0,33 do ztrans=19550 xo=50+85*(obj%6) yo=50+60*int(obj/6) d=600 drawobject(obj,rotx,roty,rotz,2.0) end
manager:machine().screens[":screen"]:draw_text(20,150,"PRESS C TO STOP RENDERING, ARROW KEYS AND Z/X TO SPIN, S TO SPIN",0xff0000ff | (math.floor((ibits/11)*0xff)<<8)| (math.floor((ibits/11)*0xff)<<16))
end -- drawbz

dispatch_list = { }

function frame_dispatcher()
for index,my_func in pairs(dispatch_list) do  my_func() end
end

function dispatch_list_remove(a_func)
for my_index,my_func in pairs(dispatch_list) do if my_func == a_func then table.remove(dispatch_list,my_index) end end
end

function print_dispatch_list() for my_index,my_func in pairs(dispatch_list) do print(my_func) end end

function cld()
dispatch_list = {}
end

table.insert(dispatch_list,drawbz)

if not alreadyregisteredframedone then
emu.register_frame_done(frame_dispatcher)
alreadyregisteredframedone=true
end

function printvertextable(i) stuckcount=0 while mem:read_u8(i)~=0xff do m=mem:read_u8(i) print(hex(i,4)..":"..hex(m).."   "..hex(getbits(m,3,7)).."  "..hex(getbits(m,0,2))) i=i+1 stuckcount=stuckcount+1 if stuckcount>100 then print("STUCK LOOP="..stuckcount)  break end end print(hex(i,4)..":"..hex(mem:read_u8(i))) end

function printpointstable(adr) a=mem:read_u8(adr) local count=0 for i=adr+1,adr+1+a-1-1,6 do  x=mem:read_i16(i) y=mem:read_i16(i+2) z=mem:read_i16(i+4) print(hex(i,4).."  vertex "..count.." ("..int(x)..","..int(y)..","..int(z)..")") count=count+1 end end

--printpointstable(0x3c5a)
--printvertextable(0x74d7)

function printline() print(string.rep("-",40)) end

function printobject(obj) 
printline() print("OBJECT "..obj.." "..battlezoneobjecttable[obj+1])  printline() 
printpointstable(tablelookup16(obj,0x388e,obj)) 
printvertextable(tablelookup16(obj,0x7472)) 
end

printobject(0xf)

-- disable game rendering by setting the displaylist to HALT 

print(manager:machine():debugger():command("wp 2000,4,w,1,{w@2000=2020;g}")) 
emu.pause()
print(manager:machine():debugger():command("go")) 
emu.unpause()




Re: Battlezone fun [Re: Golden Child] #117071 03/22/20 03:42 PM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
I was looking at the schematics and saw that there's a SLAM switch as well as a coin switch for left, center and right, so let's see if we can hook that IPT_COIN3 and IPT_TILT up:

[Linked Image from i.imgur.com]

Code
#define BZONEIN0\
        PORT_START("IN0")\
        PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_COIN1 )\
        PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_COIN2 )\
        PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_COIN3 )\
        PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_TILT )\
        PORT_SERVICE( 0x10, IP_ACTIVE_LOW )\
        PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_SERVICE1 ) PORT_NAME("Diagnostic Step") \
        /* bit 6 is the VG HALT bit. We set it to "low" */\
        /* per default (busy vector processor). */\
        PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_DEVICE_MEMBER("avg", avg_bzone_device, done_r)\
        /* bit 7 is tied to a 3kHz clock */\
        PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_MEMBER(bzone_state, clock_r)


void bzone_state::bzone_coin_counter_w(offs_t offset, uint8_t data)
{
//      machine().bookkeeping().coin_counter_w(offset,data);
        for (int i=0; i<3; i++) machine().bookkeeping().coin_counter_w(i,BIT(data,i));
}


And now coin B and C will get incremented:

(if you want to see it increment, bring up the bookkeeping, take it off with TAB, then hit 567 and hit TAB again to bring back the bookkeeping. Do it fast enough and you'll see the number increment)

Interestingly, hitting 567 simultaneously doesn't register, but 567 quickly in sequence registers.

Hitting the T for TILT causes the game to screech and ignore the coin input. (No hitting the coinbox to get free plays!)

[Linked Image from i.imgur.com]

Re: Battlezone fun [Re: Golden Child] #117072 03/23/20 04:06 AM
Joined: Feb 2014
Posts: 374
G
Golden Child Offline OP
Senior Member
OP Offline
Senior Member
G
Joined: Feb 2014
Posts: 374
One of the things that's puzzled me is why the hi-score screen for bzone is diagonal and shifted down onto the words BONUS TANK.

It's odd because the hi-score screen for bzonea is perfectly aligned.

bzone
[Linked Image from i.imgur.com]

bzonea
[Linked Image from i.imgur.com]

Apparently, the only difference between bzone and bzonea is the rendering of the hi-score screen text and how many tank icons are displayed.

For bzone, the text is rendered each line as an offset of the line above it.

For bzonea, each line of text has a CNTR command followed by a move.

Any high score above 100K will draw a tank icon in bzone.

Bzonea will draw up to 10 tanks per 100K of hi score, going off the screen to do so. (6 1/2 are visible)
If you have too many hi scores and tanks onscreen it will clip the rendering of the BONUS TANK line as it overloads the AVG.

http://www.vectorlist.org/Vectorlist/2003/07/0046.html Re: bzone and bzone2 roms - whats the diff
http://www.vectorlist.org/Vectorlist/2003/07/0046.html Re: Bug: Battlezone high score rendering


I took a hexdump and diff'ed it and got this:

Code
diff bzone_orighex.txt bzonea_orighex.txt 
55c55
< 00005340  67 45 44 52 a2 00 86 d2  8e 3c 03 bc 00 03 c4 b8  |gEDR.....<......|
---
> 00005340  70 45 44 52 a2 00 86 d2  8e 3c 03 bc 00 03 c4 b8  |pEDR.....<......|
77,86c77,86
< 000054a0  20 98 6c 20 6a 7a a0 00  a9 e0 a2 1a 20 8e 7a a2  | .l jz...... .z.|
< 000054b0  00 8e 40 03 ae 40 03 bd  00 03 85 0a 1d 01 03 f0  |..@..@..........|
< 000054c0  7c bd 01 03 85 0b a9 0a  20 9e 7b a2 1c 20 98 6c  ||....... .{.. .l|
< 000054d0  ae 40 03 bd 1e 03 20 6a  55 ae 40 03 bd 1f 03 20  |.@.... jU.@.... |
< 000054e0  6a 55 ae 40 03 bd 20 03  20 6a 55 a0 00 ae 40 03  |jU.@.. . jU...@.|
< 000054f0  bd 01 03 f0 1a ad f0 33  91 02 c8 ad f1 33 91 02  |.......3.....3..|
< 00005500  c8 ad 90 35 91 02 c8 ad  91 35 91 02 20 76 7a ae  |...5.....5.. vz.|
< 00005510  40 03 e0 1b b0 27 e8 e8  e8 8e 40 03 a9 d8 85 06  |@....'....@.....|
< 00005520  a9 ff 85 07 a9 fe 85 05  98 f0 04 a9 a3 d0 02 a9  |................|
< 00005530  f4 85 04 a9 00 85 01 20  ab 7a 4c b4 54 ad 00 0a  |....... .zL.T...|
---
> 000054a0  20 98 6c 20 6a 7a a0 00  8c 40 03 a9 e0 a2 1e 8e  | .l jz...@......|
> 000054b0  71 03 20 8e 7a ae 40 03  bd 00 03 85 0a 1d 01 03  |q. .z.@.........|
> 000054c0  f0 7b bd 01 03 85 0b a9  0a 20 9e 7b a2 1c 20 98  |.{....... .{.. .|
> 000054d0  6c ae 40 03 bd 1e 03 20  6a 55 ae 40 03 bd 1f 03  |l.@.... jU.@....|
> 000054e0  20 6a 55 ae 40 03 bd 20  03 20 6a 55 a0 00 ae 40  | jU.@.. . jU...@|
> 000054f0  03 bd 01 03 f0 24 aa ad  f0 33 91 02 c8 ad f1 33  |.....$...3.....3|
> 00005500  91 02 e0 0a 90 02 a2 0a  c8 ad 90 35 91 02 c8 ad  |...........5....|
> 00005510  91 35 91 02 ca d0 f1 20  76 7a ae 40 03 e0 1b b0  |.5..... vz.@....|
> 00005520  1c e8 e8 e8 8e 40 03 20  6a 7a ad 71 03 38 e9 0a  |.....@. jz.q.8..|
> 00005530  8d 71 03 aa a9 e0 a0 00  4c b2 54 45 52 ad 00 0a  |.q......L.TER...|


Code
cat bzone_dasm_diff.txt 
54A0: 20 98 6C jsr $6c98					54A0: 20 98 6C jsr $6c98
54A3: 20 6A 7A jsr $7a6a					54A3: 20 6A 7A jsr $7a6a
54A6: A0 00    ldy #$00						54A6: A0 00    ldy #$00
54A8: A9 E0    lda #$e0					      |	54A8: 8C 40 03 sty $0340
54AA: A2 1A    ldx #$1a					      |	54AB: A9 E0    lda #$e0
54AC: 20 8E 7A jsr $7a8e				      |	54AD: A2 1E    ldx #$1e
54AF: A2 00    ldx #$00					      |	54AF: 8E 71 03 stx $0371
54B1: 8E 40 03 stx $0340				      |	54B2: 20 8E 7A jsr $7a8e
54B4: AE 40 03 ldx $0340				      |	54B5: AE 40 03 ldx $0340
54B7: BD 00 03 lda $0300, x				      |	54B8: BD 00 03 lda $0300, x
54BA: 85 0A    sta $0a					      |	54BB: 85 0A    sta $0a
54BC: 1D 01 03 ora $0301, x				      |	54BD: 1D 01 03 ora $0301, x
54BF: F0 7C    beq $553d				      |	54C0: F0 7B    beq $553d
54C1: BD 01 03 lda $0301, x				      |	54C2: BD 01 03 lda $0301, x
54C4: 85 0B    sta $0b					      |	54C5: 85 0B    sta $0b
54C6: A9 0A    lda #$0a					      |	54C7: A9 0A    lda #$0a
54C8: 20 9E 7B jsr $7b9e				      |	54C9: 20 9E 7B jsr $7b9e
54CB: A2 1C    ldx #$1c					      |	54CC: A2 1C    ldx #$1c
54CD: 20 98 6C jsr $6c98				      |	54CE: 20 98 6C jsr $6c98
54D0: AE 40 03 ldx $0340				      |	54D1: AE 40 03 ldx $0340
54D3: BD 1E 03 lda $031e, x				      |	54D4: BD 1E 03 lda $031e, x
54D6: 20 6A 55 jsr $556a				      |	54D7: 20 6A 55 jsr $556a
54D9: AE 40 03 ldx $0340				      |	54DA: AE 40 03 ldx $0340
54DC: BD 1F 03 lda $031f, x				      |	54DD: BD 1F 03 lda $031f, x
54DF: 20 6A 55 jsr $556a				      |	54E0: 20 6A 55 jsr $556a
54E2: AE 40 03 ldx $0340				      |	54E3: AE 40 03 ldx $0340
54E5: BD 20 03 lda $0320, x				      |	54E6: BD 20 03 lda $0320, x
54E8: 20 6A 55 jsr $556a				      |	54E9: 20 6A 55 jsr $556a
54EB: A0 00    ldy #$00					      |	54EC: A0 00    ldy #$00
54ED: AE 40 03 ldx $0340				      |	54EE: AE 40 03 ldx $0340
54F0: BD 01 03 lda $0301, x				      |	54F1: BD 01 03 lda $0301, x
54F3: F0 1A    beq $550f				      |	54F4: F0 24    beq $551a
54F5: AD F0 33 lda $33f0				      |	54F6: AA       tax
54F8: 91 02    sta ($02), y				      |	54F7: AD F0 33 lda $33f0
54FA: C8       iny					      |	54FA: 91 02    sta ($02), y
54FB: AD F1 33 lda $33f1				      |	54FC: C8       iny
54FE: 91 02    sta ($02), y				      |	54FD: AD F1 33 lda $33f1
5500: C8       iny					      |	5500: 91 02    sta ($02), y
5501: AD 90 35 lda $3590				      |	5502: E0 0A    cpx #$0a
5504: 91 02    sta ($02), y				      |	5504: 90 02    bcc $5508
5506: C8       iny					      |	5506: A2 0A    ldx #$0a
5507: AD 91 35 lda $3591				      |	5508: C8       iny
550A: 91 02    sta ($02), y				      |	5509: AD 90 35 lda $3590
550C: 20 76 7A jsr $7a76				      |	550C: 91 02    sta ($02), y
550F: AE 40 03 ldx $0340				      |	550E: C8       iny
5512: E0 1B    cpx #$1b					      |	550F: AD 91 35 lda $3591
5514: B0 27    bcs $553d				      |	5512: 91 02    sta ($02), y
5516: E8       inx					      |	5514: CA       dex
5517: E8       inx					      |	5515: D0 F1    bne $5508
5518: E8       inx					      |	5517: 20 76 7A jsr $7a76
5519: 8E 40 03 stx $0340				      |	551A: AE 40 03 ldx $0340
551C: A9 D8    lda #$d8					      |	551D: E0 1B    cpx #$1b
551E: 85 06    sta $06					      |	551F: B0 1C    bcs $553d
5520: A9 FF    lda #$ff					      |	5521: E8       inx
5522: 85 07    sta $07					      |	5522: E8       inx
5524: A9 FE    lda #$fe					      |	5523: E8       inx
5526: 85 05    sta $05					      |	5524: 8E 40 03 stx $0340
5528: 98       tya					      |	5527: 20 6A 7A jsr $7a6a
5529: F0 04    beq $552f				      |	552A: AD 71 03 lda $0371
552B: A9 A3    lda #$a3					      |	552D: 38       sec
552D: D0 02    bne $5531				      |	552E: E9 0A    sbc #$0a
552F: A9 F4    lda #$f4					      |	5530: 8D 71 03 sta $0371
5531: 85 04    sta $04					      |	5533: AA       tax
5533: A9 00    lda #$00					      |	5534: A9 E0    lda #$e0
5535: 85 01    sta $01					      |	5536: A0 00    ldy #$00
5537: 20 AB 7A jsr $7aab				      |	5538: 4C B2 54 jmp $54b2
553A: 4C B4 54 jmp $54b4				      |	553B: 45 52    eor $52
553D: AD 00 0A lda $0a00					553D: AD 00 0A lda $0a00
5540: 4A       lsr a						5540: 4A       lsr a
5541: 4A       lsr a						5541: 4A       lsr a
5542: 4A       lsr a						5542: 4A       lsr a
5543: 4A       lsr a						5543: 4A       lsr a
5544: 29 03    and #$03						5544: 29 03    and #$03
5546: F0 1C    beq $5564					5546: F0 1C    beq $5564
5548: AA       tax						5548: AA       tax
5549: BD 86 38 lda $3886, x					5549: BD 86 38 lda $3886, x
554C: 18       clc						554C: 18       clc
554D: F8       sed						554D: F8       sed
554E: 69 01    adc #$01						554E: 69 01    adc #$01




So if we change a few memory locations we can get the hiscore of bzone to perfectly match the output of bzonea but there will still be only one tank rendered.


Here's a little lua routine to mimic the self-test routine of bzone:

Code
function hex(a,digits) digits = digits or 2 return string.format("%0"..digits.."x",a) end

mem2=manager:machine():memory().regions[":maincpu"]
mem=mem2

function bzself()
-- self test initializes to 0xff and then xors every byte in each 0x800 block, after each block the value should be zero, skipping blocks 0x4000-0x47ff and 0x4800-0x4fff
for j=0x0,0x9 do o=0xff for i=0x3000+j*0x800,0x3000+j*0x800+0x7ff do o=o~mem:read_u8(i) end print(j,hex(0x3000+j*0x800),o) end -- self test at $7d01
bzmemlocs={0x5530,0x54a9,0x54ab,0x552c,0x5340}
bzmemlocsdesc={
"hiscore horiz move for next line",
"hiscore first line x pos",
"hiscore first line y pos",
"hiscore horiz move next line w/tank icon",
"self test eor checksum fixer"}
print("-------------")
for i=1,#bzmemlocs do print(hex(i),hex(bzmemlocs[i]),hex(mem2:read_u8(bzmemlocs[i])),bzmemlocsdesc[i]) end
end


On initial start we get:
Code
bzself()
0	3000	0
1	3800	0
2	4000	255
3	4800	255
4	5000	0
5	5800	0
6	6000	0
7	6800	0
8	7000	0
9	7800	0
-------------
01	5530	f4	hiscore horiz move for next line
02	54a9	e0	hiscore first line x pos
03	54ab	1a	hiscore first line y pos
04	552c	a3	hiscore horiz move next line w/tank icon
05	5340	67	self test eor checksum fixer



mem2:write_u8(0x5530,0xf8)   -- f4 to f8  0100 to 1000
mem2:write_u8(0x54ab,0x1e)   -- 1a to 1e  1010 to 1110
mem2:write_u8(0x552c,0xa7)   -- a3 to a7  0011 to 0111
mem2:write_u8(0x5340,0x6b)   -- fix the self test checksum


bzself()
0	3000	0
1	3800	0
2	4000	255
3	4800	255
4	5000	0
5	5800	0
6	6000	0
7	6800	0
8	7000	0
9	7800	0
-------------
01	5530	f8	hiscore horiz move for next line
02	54a9	e0	hiscore first line x pos
03	54ab	1e	hiscore first line y pos
04	552c	a7	hiscore horiz move next line w/tank icon
05	5340	6b	self test eor checksum fixer



If you didn't care about it matching bzonea exactly, you could probably find some values that don't change the eor checksum and look reasonable on screen.


Then we get a high score screen that matches:

Loaded the fixed bzone and the bzonea and overlaid it with gimp and modified the opacity to see how it lines up.

[Linked Image from i.imgur.com]

Page 1 of 2 1 2

Who's Online Now
3 registered members (Colin Howell, Dam0, 1 invisible), 89 guests, and 1 spider.
Key: Admin, Global Mod, Mod
ShoutChat Box
Comment Guidelines: Do post respectful and insightful comments. Don't flame, hate, spam.
Forum Statistics
Forums9
Topics8,764
Posts115,179
Members4,889
Most Online890
Jan 17th, 2020
Powered by UBB.threads™ PHP Forum Software 7.7.3