Posts for Bisqwit


Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
errror1 wrote:
well just counting the avis it's 293gb spread across five hard drives.
In a single logical volume?
Post subject: Re: How big is your ...
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
slo_bro wrote:
... AVI folder
48 gigabytes currently. Then, 9 gigabytes in the cromfs archive containing... helpful files for watching the movie files for various consoles. 13 megabytes in torrent directories (plus numerous backups and the git database). 44 megabytes in source code directories (+git database) -- I don't suppose many have this though. 463 megabytes on the media site (screenshots, article images and WIP/demo movies) EDIT: For comparison, 17 gigabytes where stuff downloaded from SDA is.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
symbolix X has now been changed into pixel-go-round.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Warp wrote:
Some people have way too much time in their hands... ;)
For some reason I feel particularly productive with useless programs lately. I expect it to subside any moment now.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
pirate_sephiroth wrote:
<passive aggression>
huh?
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
I have reverted the voting system now to the old "yes / no / meh" system. With any submission that are submitted after this, the voting question will be changed to "should this movie be published", but it will remain "did you like this movie" for the current ones.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
[img_left]http://bisqwit.iki.fi/kala/snap/qbasicnovatron.png[/img_left][img_right]http://bisqwit.iki.fi/kala/snap/novatron_000.png[/img_right] There was once this game called NOVATRON. I still suck at it btw. This is something like that except with no sounds and with a weaker AI. On the left: QuickBasic game, source code below On the right: Original Novatron
DEFINT A-Z
DECLARE SUB plot(screenx, screeny)
DECLARE SUB setp(fieldx,fieldy, pal)
DECLARE FUNCTION shadows(fieldx,fieldy,fieldz)
DECLARE SUB AI(botx,boty,botdir)
CONST xdim = 200
CONST ydim = 110
CONST wallheight = 15
TYPE dircfg
  x AS INTEGER
  y AS INTEGER
  k AS STRING*1
END TYPE
OPTION BASE 0
DIM SHARED fld(xdim-1, ydim-1), palettes(1 TO 3,9), dircfg(3) AS dircfg
SCREEN 13 ' 320x200 256-color mode, very popular

' configure input and axial directions
DATA 0,-1,H, 0,1,P, -1,0,K, 1,0,M
FOR n=0 TO 3: READ dircfg(n).x, dircfg(n).y, dircfg(n).k: NEXT

' Colors
FOR p=1 TO 3:FOR c=0 TO 9:READ palettes(p,c):NEXT c,p
DATA 11, 11,3, 3,1,  9, 9,1, 3,1 : REM Wall colours
DATA 14, 14,6, 6,4, 12,12,4, 6,4 : REM Protagonist colours
DATA 13, 13,5, 5,1,  9, 9,1, 3,1 : REM Antagonist colours

' Create edge walls
FOR y = 0 TO ydim - 1: fld(0,y)=1: fld(xdim-1,y)=1: NEXT
FOR x = 0 TO xdim - 1: fld(x,0)=1: fld(x,ydim-1)=1: NEXT
' Render the entire field
FOR y = 50 TO 199-20
  FOR x = 50 TO 319-30
    plot x,y
NEXT x,y

botx=xdim*3\4  : yourx=xdim\2
boty=ydim*3\4  : youry=ydim\2

dir=3
win=0
botdir=2
DO
  IF fld(botx,boty) THEN win=1: EXIT DO
  setp botx,boty, 3

  IF fld(yourx,youry) THEN win=0: EXIT DO
  setp yourx,youry, 2
  y$ = RIGHT$(INKEY$,1)
  IF y$ = CHR$(27) THEN win=2: EXIT DO
  FOR n=0 TO 3
    IF y$ = dircfg(n).k THEN dir = n
  NEXT
  AI botx,boty,botdir
  yourx = yourx + dircfg(dir).x
  youry = youry + dircfg(dir).y
  botx = botx + dircfg(botdir).x
  boty = boty + dircfg(botdir).y
LOOP
IF win=1 THEN PRINT "You win" ELSE IF win=0 THEN PRINT "You lose"
WHILE INKEY$<>"":WEND
PRINT "Game over; press any key"; : s$ = INPUT$(1)
SCREEN 0,1,0,0 : WIDTH 80,25

END

' Field is mapped with fieldx,fieldy. fieldz = vertical(0=root of wall, positive=up).
' Screen is mapped with screenx,screeny.
' 
' screenx = fieldx + fieldy*0.3 + 50
' screeny = fieldy - fieldz     + 70
'
' This gives us:
'  fieldy  =            (screeny+fieldz-70)
'  fieldx  = screenx - ((screeny+fieldz-70)*0.3 + 50)
'
' The light vector is (field coordinates) x=0.4, y=0.6, z=0.2
'
SUB plot(screenx, screeny)
  xorpt = 1 AND (screenx XOR screeny)
  FOR fieldz = wallheight TO 0 STEP -1
    fieldy = (screeny + fieldz - 70)  
    fieldx = screenx - (fieldy*3\10 + 50)
    IF  fieldx>=0 AND fieldx<xdim _ 
    AND fieldy>=0 AND fieldy<ydim THEN
      pal = fld(fieldx,fieldy)
      IF pal THEN
        palindex = 5 ' Default wall colour
        IF fieldz >= wallheight THEN palindex = 0 ' Top of the wall
        ' Check shadow
        SELECT CASE shadows(fieldx,fieldy,fieldz)
          CASE 1: palindex = palindex + 1+xorpt 'shadowing thing on the right side
          CASE 2: palindex = palindex + 3+xorpt 'shadowing thing on the left side
        END SELECT
        ' Plot the wall pixel
        PSET(screenx,screeny), palettes(pal,palindex)
        EXIT SUB
      END IF
    ELSE
      IF fieldz=0 THEN EXIT SUB 'always out of bounds
      IF fieldx<-15 OR fieldy<-15 THEN EXIT SUB
      IF fieldx>=xdim+15 OR fieldy>=ydim+15 THEN EXIT SUB
    END IF
  NEXT
  'Plot floor   
  c = xorpt
  fieldz = 0
  fieldy = (screeny + fieldz - 70)
  fieldx = screenx - (fieldy*3\10 + 50)
  IF shadows(fieldx,fieldy,fieldz) THEN
    c = c AND ((2 AND (screenx XOR screeny))\2) ' any shadow
  END IF
  PSET (screenx,screeny), c
END SUB

' Calculate whether there is something that shadows this spot
FUNCTION shadows(fieldx,fieldy,fieldz)
  FOR shadowvec = 1 TO wallheight ' (upper limit must be >= ceil(wallheight))
    fx = CINT(fieldx + 0.4 * shadowvec)
    fy =     (fieldy +       shadowvec)   
    fz =     (fieldz +       shadowvec)
    IF fz > wallheight OR fx >= xdim OR fy >= ydim THEN EXIT FOR ' out of bounds
    IF fld(fx, fy) THEN shadows = 1: EXIT FUNCTION
  NEXT
  FOR shadowvec = 1 TO wallheight ' (upper limit must be >= ceil(wallheight))
    fx = CINT(fieldx - 0.7 * shadowvec)
    fy =     (fieldy +       shadowvec)
    fz =     (fieldz +       shadowvec)
    IF fz > wallheight OR fx < 0 OR fy >= ydim THEN EXIT FOR ' out of bounds
    IF fld(fx, fy) THEN shadows = 2: EXIT FUNCTION
  NEXT  
END FUNCTION

' Set this pixel and plot the field
SUB setp(fieldx,fieldy, pal)
  fld(fieldx,fieldy)=pal
  screenx = fieldx+fieldy*0.3+50
  screeny = fieldy          +70
  FOR x=screenx-12 TO screenx+8
    FOR y=screeny-23 TO screeny   
      plot x,y
  NEXT y,x
END SUB

' The computer player's AI. Quite bad.
SUB AI(botx,boty, botdir)
  DIM dists(3)
  FOR n=0 TO 3: dists(n) = 9: NEXT
  FOR dist=8 TO 1 STEP -1
    FOR n=0 TO 3
      IF fld(botx + dist*dircfg(n).x, boty + dist*dircfg(n).y) THEN dists(n) = dist
  NEXT n,dist
  ' Go to whichever direction that is furthest away from any walls
  IF RND > 0.93 THEN botdir = INT(RND*4) 'randomly change direction
  bestdist=0: dir=botdir
  FOR n=0 TO 3
    testdir=(botdir+n)AND 3
    IF dists(testdir) > bestdist THEN bestdist=dists(testdir): dir=testdir
  NEXT
  botdir=dir AND 3
END SUB
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Xkeeper wrote:
The end block is still somewhat nonsensical
KEY ON = puts on the display of function key labels at the screen bottom KEY OFF = hides those In GW-BASIC, hitting F1 gives you "LIST ", hitting F2 gives you "RUN" and a newline, hitting F3 gives you "LOAD" and a quote character, and so on. Those are always displayed on the screen bottom, even when the program runs. However, KEY ON/OFF can show/hide them. The macroes can also be redefined if one so chooses ― for example, KEY 1, "GRUU" changes the F1 key to produce the string "GRUU". Try this (in either GW-BASIC or QBASIC):
10 KEY 1, "Mary"
20 KEY 2, "had"
30 KEY 3, "a"
40 KEY 4, "little"
50 KEY 5, "lamb"
60 KEY 6, " "
70 KEY ON
80 PRINT "Hit F1, F6, F2, F6, F3, F6, F4, F6 and F5, in this order."
90 INPUT s$
EDIT: Or if you are referring to the untrans function… well, it does what it must do in no particularly elegant manner.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Derakon wrote:
I'm curious what the DATA block does. Is it a set of instructions for how to draw the image? A bitmap?
It is a set of instructions. Each instruction is 6 characters. The first character denotes the operation type: B = draw filled rectangle, L = draw line. The second character denotes the colour. [0,1,2,3] = [black,cyan,red,white]. The next four characters denote the coordinates. x1,y1,x2,y2. The coordinates are in the range 0<=x<46 and y<=0<60. A coordinate is translated into a character by adding 33 to it ("!"). If the result produces ":" (command separator), a "^" is produced instead. If the result produces "," (data separator), a "`" is produced instead. The subroutine at line 98 does the reverse of this transformation
Xkeeper wrote:
I still prefer QBASIC over this.
QBASIC is really not very much different. In fact, this very same code works fine in QBASIC without modifications. The parser abuses mentioned at the end of the FPS renderer however wouldn't work without changes. Though if you want to write the Mario renderer in a QBASICcy way, it would become like this:
DEFINT a-z
DECLARE FUNCTION untrans(k AS STRING)
SCREEN 1
PALETTE 2, 12  'Needed for CGA compatibility
RESTORE imagedata
DO
  READ s$
  IF s$ = "z" THEN EXIT DO
  FOR a=1 TO 55 STEP 6 
    FOR basey = 0 TO 200-60 STEP 60
    FOR basex = 0 TO 320-40 STEP 46
      x1 = untrans(MID$(s$,a+2,1)) + basex
      y1 = untrans(MID$(s$,a+3,1)) + basey
      x2 = untrans(MID$(s$,a+4,1)) + basex
      y2 = untrans(MID$(s$,a+5,1)) + basey
      c = VAL(MID$(s$,a+1,1))
      IF     MID$(s$,a,1) = "B" THEN
        LINE (x1,y1)-(x2,y2),c,BF
      ELSEIF MID$(s$,a,1) = "L" THEN
        LINE (x1,y1)-(x2,y2),c
      ELSE
        PRINT "DATA ERROR("; MID$(s$,a,6); ")"
        EXIT DO
      END IF
    NEXT basex, basey
  NEXT
LOOP
LOCATE 24: KEY OFF: PRINT "Press any key";: s$=INPUT$(1): KEY ON
SCREEN 0,1,0,0: WIDTH 80,25
END

imagedata:
   DATA B2!!N\B3!!'NB3H!N\B3!!1+B0C.L3B09<?QB0'C+QB3-F9LB3DOG\B0#PCR
   DATA B3!!G"B0)AKBB31S3\B0KCLLB1![C[B0`M/ZB0H8I=B3ADKJB01048B05UCW
   DATA B0+=0?B1/NJNB09#B$B20)G/B0+-I-B07MJMB2$QBQB05YBYB0(/)9B3!!6%
   DATA B3!<*@B079^<B0E%E6L0)09#L3)7BGL088GKL0&FG?L04/H7L3!9DPL343E>
   DATA L0"S%\L01><OL0C$H0L3>2JAL3!R-4B26VAVL079DTL0*\+^L2+A-YB0GDGG
   DATA L3"29"L20.>ML0<7=/L0(/FBB3E!G%L0&N(\B3/Z8\L04U8LL2``J0L02I4J
   DATA L32=^3L0'73'L3=7H^B3DBECL2`@1DL0&G8UL363A>L1+36=L06.M2B3!X"\
   DATA L1=+@'L1'B)LL3!P)(L0"U'[L12B<NB0E%E5L0(9^GL3#O*4B3=C?CL3C!M/
   DATA L0/Q=NL14Q9OL0=FI=L1;GFLL25AENL3>MCOB2*F*IB2</H/L3-F5DB0K0M1
   DATA L2$R*NL0"R`[L00*^$B3>(B(B3BZC\L0&I.OL3>5IDL05PD?L28^?AL2)9`0
   DATA B3!!.`L1/M3LL05QDSL0"W#YL33N7ML3F4K^L30X<\B0KEKEL2-B1AB3!<*@
   DATA L07^;CL0D%F)L0/DG?L2<ODUB0.7.9L3*51;L2);17B0B4B6L13@4BL0<)>'
   DATA L2%T+OL2>=A@L0/156L3%+=!L3?3B2L02L3ML0'L0UB3<X@XL0?ZBXL3@GGK
   DATA z

FUNCTION untrans(k AS STRING)
  v = ASC(k)
  IF v = 96 THEN v = 11 ELSE IF v = 94 THEN v = 25 ELSE v = v - 33
  untrans = v
END FUNCTION
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Derakon wrote:
I'm curious what the DATA block does. Is it a set of instructions for how to draw the image? A bitmap? I might be able to figure it out myself if I knew what MID does, but there's also those $ and : businesses. Who puts $ after the variable name, anyway?
: separates statements. DATA declares data storage. A RESTORE statement can be used to alter the global pointer from which data is read, and successive READ statements will fetch an item from the data storage, incrementing the global pointer in the process. I.e.
10 DATA 1
20 READ a,b
30 READ c
40 DATA 2,3
This reads the values 1 to a, 2 to b and 3 to c. w=MID$(x,y,z) assigns a z-character substring of x into w, starting from position y (1-based). In BASIC, there are five basic data types. Each datatype is denoted by a suffix in the variable name: A% is INT, A! is SINGLE, A# is DOUBLE, A& is LONG and A$ is STRING. Every single variable name can exist in any of these data type spaces. For example, you can have A% and A$ is the same problem. A variable, without a suffix, is one of these. Which one it is, is determined by the DEF statement. DEFINT A-Z declares that all suffixless variables that begin with a letter A to Z are INTEGER. The specification defaults to SGN, so in a DEF-less program, all suffixless variables are SINGLE types. Example:
10 DEFINT C
20 A = 5      ' defaults to A! (DEFSNG)
30 C = 10    ' defaults to C% (DEFINT)
35 A! = 15   ' same as A
40 C! = 20   ' unassigned earlier
45 C% = 25 ' same as C
50 PRINT A; C; A!; C!; C%
This prints " 15 25 15 20 25 ". If you want a string variable and you haven't used DEFSTR for the starting character of that variable name, you must use $ to explicitly identify the type. By convention, for some reason, built-in functions returning a string value are all named with the $ suffix.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Xkeeper wrote:
It isn't so much "hiding a lot of meaning" as "code density".
Meh. I guess it's just still a bit of my Finnish background showing through. I prefer "a feather-decorated blue hat donning man" rather than "a man who dons a hat that is decorated with a feather and is blue" or "there is a hat. the hat is blue. there is a feather. the hat is decorated with the feather. there is a man. the man dons the hat."
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Xkeeper wrote:
I could probably understand it if I opened it in a text editor and read over it a few times, but right now holy crap that code gives me a headache.
Methinks your head aches a bit too easily. But that's so little code, and in BASIC, you cannot hide a lot of meaning to little code, unlike in e.g. Perl!
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Solon wrote:
moozooh wrote:
Do submit!
5char
Ubmit?
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
[img_left]http://bisqwit.iki.fi/kala/snap/vgbisq_cropped_resized.jpg[/img_left] Re: This picture ― reposting due to page change.
Randil wrote:
Haha, I actually recognized that room immediately (how you got the idea to make that picture is a little unclear to me).
Oh, well it is a little unclear to me too. When I saw the scene in the game, I just thought immediately of taking that picture. Mostly because of the matching photorealistic quality and the lighting conditions that are easily emulated. I haven't actually played forward from that point yet.
MUGG wrote:
Google Translator is your friend. :P
Yes… Though Finnish language is not a member of Indo-European languages, and consequently mechanical translators designed with Indo-European (Germanic and Romance) internal structures are usually less than successful at conveying the meaning either way, in this particular case, Google Translator did adequately. (Incidentally, the above sentence, though grammatically correct, is written so that the first reading of it by a human person will usually be a misparse, due to the assumption that the first "though" binds to "Yes", when in fact it binds to the clause starting from "in this".) Next I'll probably try to find a scene from Chrono Cross. That's quite difficult because in most scenes the perspective is bad: Either too far away, or a too vertical angle. Or too cartoonish; not looking like a toon character I wouldn't fit. Or which I don't have blankets of matching colour (for cutting without edge artifacts) for :] For anyone interested, I created the above picture in these ten steps: Ⅰ) Find a scene in a game. Ⅱ) Take lots of screenshots from the scene. Move the player character around so you get screenshots from every corner of the room. Try to ensure that every nook is pictured at least once without the player character or his shadow obstructing the view. Ⅲ) Use gimp to composite the screenshots into a bigger background picture. Ⅳ) Gather equipment: A digital camera; blankets and towels of the colours matching those that are right behind yourself in the spot of scene where you want to be placed, when viewed from the camera; proper light sources (I used a 500W halogen lamp ― you need bright lights unless your camera is good at taking pictures in roomlight). Dress up in a way appropriate for the scene and apply makeup if necessary ;) Ⅴ) Situate yourself, the camera, and the light source(s) in such a spatial relationship that matches those found in the scene. Ensure that everything is oriented in a way matching the scene. Lay the blankets and towels behind you so that from the viewpoint of the camera, every part of you is situated in front of them. I put my camera on top of a book shelf (though it should have been higher). I sat on my bed. Be wary of placing the camera too near you, because it distorts your proportions. Using optical zoom is better. Take a picture. If you can be perfectly still, don't use camera flash, but instead, use a long exposure time (I'm not sure but I probably used ⅓s or so). The colour depth will be better and the grain artifacts fewer. Ⅵ) Use gimp and add alpha channel to the photo, and then carefully cut out everything that's not you, in the photo. The crop, lasso, eraser and the magic wand tools are all good for that purpose. Use zoom for details. Try to preserve the edges of the hair. Ⅶ) Take the background picture, add a layer and paste your picture to the new layer. Scale your picture and rotate as needed to match the right proportions and angle for the scene. If you can't get it fit, restart from step Ⅴ. Ⅷ) Adjust the colour balance of the layer with your picture in it (gimp: colour tools, colour curves for each individual channel, and then hue and color saturation) until it fits the lighting conditions in the background picture. Ⅸ) Add a layer between your picture and the background, and plot shadows using the (air)brush tool. Draw the diffuse reflections too. (E.g. if you're standing against a lit red wall, the red is also reflected upon you and thus you should add some red tint to your picture. Conversely, your clothes may reflect some coloured light upon the surfaces in the scene.). Adjust the edge smoothness of the brush and the opacity of it as needed. Ⅹ) Save the result, post it everywhere.
Post subject: Mario Mario Mario Mario Mario Mario Mario
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Mario Mario Mario Mario Mario Mario Mario [img_left]http://bisqwit.iki.fi/kala/snap/gwbasicmario.png[/img_left] Constructed as follows:
1 DEFINT a-z
10 SCREEN 1:RESTORE 80
20 READ s$
30 IF s$="z" THEN 80
40 FOR a=1 TO 55 STEP 6
45   basex=0:basey=0
50   k$=MID$(s$,a+2,1):GOSUB 98:x1=v+basex
51   k$=MID$(s$,a+3,1):GOSUB 98:y1=v+basey
52   k$=MID$(s$,a+4,1):GOSUB 98:x2=v+basex
53   k$=MID$(s$,a+5,1):GOSUB 98:y2=v+basey
54   c=VAL(MID$(s$,a+1,1))
55   IF MID$(s$,a,1)="B" THEN LINE(x1,y1)-(x2,y2),c,BF:GOTO 60
56   IF MID$(s$,a,1)="L" THEN LINE(x1,y1)-(x2,y2),c   :GOTO 60
59   PRINT "DATA ERROR(";MID$(s$,a,6);")":GOTO 98
60   basex=basex+46:IF basex+40 < 320 THEN 50
61   basey=basey+60:basex=0:IF basey+60 < 200 THEN 50
62 NEXT
70 GOTO 20
80 DATA B2!!N\B3!!'NB3H!N\B3!!1+B0C.L3B09<?QB0'C+QB3-F9LB3DOG\B0#PCR
81 DATA B3!!G"B0)AKBB31S3\B0KCLLB1![C[B0`M/ZB0H8I=B3ADKJB01048B05UCW
82 DATA B0+=0?B1/NJNB09#B$B20)G/B0+-I-B07MJMB2$QBQB05YBYB0(/)9B3!!6%
83 DATA B3!<*@B079^<B0E%E6L0)09#L3)7BGL088GKL0&FG?L04/H7L3!9DPL343E>
84 DATA L0"S%\L01><OL0C$H0L3>2JAL3!R-4B26VAVL079DTL0*\+^L2+A-YB0GDGG
85 DATA L3"29"L20.>ML0<7=/L0(/FBB3E!G%L0&N(\B3/Z8\L04U8LL2``J0L02I4J
86 DATA L32=^3L0'73'L3=7H^B3DBECL2`@1DL0&G8UL363A>L1+36=L06.M2B3!X"\
87 DATA L1=+@'L1'B)LL3!P)(L0"U'[L12B<NB0E%E5L0(9^GL3#O*4B3=C?CL3C!M/
88 DATA L0/Q=NL14Q9OL0=FI=L1;GFLL25AENL3>MCOB2*F*IB2</H/L3-F5DB0K0M1
89 DATA L2$R*NL0"R`[L00*^$B3>(B(B3BZC\L0&I.OL3>5IDL05PD?L28^?AL2)9`0
90 DATA B3!!.`L1/M3LL05QDSL0"W#YL33N7ML3F4K^L30X<\B0KEKEL2-B1AB3!<*@
91 DATA L07^;CL0D%F)L0/DG?L2<ODUB0.7.9L3*51;L2);17B0B4B6L13@4BL0<)>'
92 DATA L2%T+OL2>=A@L0/156L3%+=!L3?3B2L02L3ML0'L0UB3<X@XL0?ZBXL3@GGK
93 DATA z
94 LOCATE 24:KEY OFF:PRINT "Press any key";:s$=INPUT$(1):KEY ON
95 SCREEN 0: WIDTH 80,25: END
98 v=ASC(k$):IF v=96 THEN v=11 ELSE IF v=94 THEN v=25 ELSE v=v-33
99 RETURN
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Kirkq wrote:
One more thing. Regardless of NES/SNES it seems to me like it would be strategic to pump the down button at specific frames while doing other actions, or in between actions in order to make bombs fall faster. This would surely speed up Bisqwit's WIP if it works. It may very well not work though.
I have used this. However, having an extra bomb on the screen when the round is cleared costs about 40 frames, so it's not a good idea in all cases.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Post subject: Re: Bisqwit's Patch = Black X Window. :(
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
OmnipotentEntity wrote:
In fairness, on my system, no Bisqwit patch = compile error.
Which "Bisqwit's patch" are you referring to?
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Bisqwit wrote:
It doesn't produce optimized code, though. Redundant memory stores abound.
Fixing that problem is rather complicated as shown below, but oddly rewarding.
#include <string>
#include <iostream>
#include <vector>
#include <ctype.h>
#include <cstdlib>
#include <cstring>

#ifndef _WIN32
# include <sys/mman.h>
#else
# include <windows.h>
#endif

#ifdef __SSE2__
typedef double FloatType;
#else
typedef float FloatType;
#endif

enum {
    Reg64Prefix = sizeof(long) == 8 ? 0x48 : 0x90,   // A prefix for 64-bit opcodes, or nop on 32-bit
    SSEopCode = sizeof(FloatType) == 8 ? 0xF2 : 0xF3 // 0xF3 = movss/addss/etc, 0xF2 = movsd/addsd/etc
};

/* These functions synthesize some x86 opcodes. */
static unsigned char SSEopBuffer[9] = {SSEopCode,0x0F,0,0, 0,0,0,0};
static unsigned char* SSEop_regpair(int opno,int xmmreg1,int xmmreg2)
{
    SSEopBuffer[2] = opno;
    SSEopBuffer[3] = 0xC0 + xmmreg1*8 + xmmreg2;
    return SSEopBuffer;
}
static unsigned char* SSEop_regandmem8(int opno,int xmmreg, int indexreg,int offset)
{
    SSEopBuffer[2] = opno;
    SSEopBuffer[3] = 0x40 + xmmreg*8 + indexreg;
    if(indexreg == 4) /* sp */
        { SSEopBuffer[4] = 0x24;
          SSEopBuffer[5] = offset; }
    else
        SSEopBuffer[4] = offset;
    return SSEopBuffer;
}
static unsigned char* SSEop_regandmem32(int opno,int xmmreg, int indexreg,int offset)
{
    SSEopBuffer[2] = opno;
    SSEopBuffer[3] = 0x80 + xmmreg*8 + indexreg;
    if(indexreg == 4) /* sp */
        { SSEopBuffer[4] = 0x24;
          *(int*)&SSEopBuffer[5] = offset; }
    else
        *(int*)&SSEopBuffer[4] = offset;
    return SSEopBuffer;
}

/* This structure tells where the given value is stored */
struct Where
{
    enum {
        stack, // r = SP offset where the information is found
        immed, // r = DX offset where the information is found
        xmm    // r = XMM register number where the information is found
    } pos;
    int r; // type-specific information about the location
    
    struct stack_tag{}; struct immed_tag{}; struct xmm_tag{};
    Where() : pos(xmm),r(-1) { }
    Where(stack_tag, int o) : pos(stack),r(o) { }
    Where(immed_tag, int o) : pos(immed),r(o) { }
    Where(xmm_tag, int o) : pos(xmm),r(o) { }
};

#define EmitValAtN(offs, ptr, N) do { \
    const unsigned char* atp = (ptr); \
    codebuf.insert(codebuf.begin()+(offs), atp, atp+N); } while(0)
#define EmitValAt(offs, ptr) EmitValAtN(offs, (const unsigned char*)&(ptr), sizeof(ptr))
#define Emit(seq) EmitValAt(codebuf.size(), seq)
#define EmitN(seq,N) EmitValAtN(codebuf.size(), seq, N)
#define EmitSSEMemOp(opno,xmmreg,indexreg,offset) \
    if(offset >= -128 && offset < 128) \
        EmitN(SSEop_regandmem8(opno,xmmreg,indexreg,offset), 5+((indexreg)==4)); \
    else \
        EmitN(SSEop_regandmem32(opno,xmmreg,indexreg,offset), 8+((indexreg)==4))
#define EmitSSEMemOpWith(opno, xmmregno, what) \
    EmitSSEMemOp(opno, xmmregno, what.pos == Where::immed ? 2/*dx*/ : 5/*bp*/, what.r)

/* This object deals with the concept of "stack calculator".
 * Though it theoretically allows random access within
 * the stack, it is only used in pure stacky way.
 * Each slot in the stack may be stored in hardware stack,
 * in the table of constants, or in an XMM register.
 */
class Stack
{
    std::vector<Where> mathstack;
    size_t max_hwstack_size;
public:
    Stack() : mathstack(),max_hwstack_size(0) { }
    
    void Push(const Where& w)
    {
        mathstack.push_back(w);
    }
    
    /* Produces a mathematical binary operation between the two
     * topmost mathematical stack items.
     */
    void Binop(std::vector<unsigned char>& codebuf, int op, bool commutative)
    {
        Where top1 = mathstack[mathstack.size()-1];
        Where top2 = mathstack[mathstack.size()-2];
        if(commutative && top1.pos == Where::xmm && top2.pos != Where::xmm)
        {
            // Swap the operands to make it more favorable
            Where tmp = top1; top1 = top2; top2 = tmp;
        }
        // top2 is the "target" operand. For example, top2 /= top1.

        /*fprintf(stderr, "top1 is %s%d, top2 is %s%d\n",
            "stack\0immed\0xmm\0"+6*top1.pos, top1.r,
            "stack\0immed\0xmm\0"+6*top2.pos, top2.r);*/
        
        int result_reg = -1;
        if(top1.pos != Where::xmm) // if top1, i.e. rhs, is a memory operand.
        {
            // subss xmm_a, [top2]
            int xmm_a = Load(codebuf, top2, -1);
            EmitSSEMemOpWith(op, result_reg = xmm_a, top1);
        }
        else
        {
            // movss xmm_b, [top2] -- if needed
            // (movss xmm_a, [top1] -- not needed)
            // subss xmm_b, xmm_a
            int xmm_b = Load(codebuf, top2, top1.pos==Where::xmm ? top1.r : -1);
            int xmm_a = Load(codebuf, top1, xmm_b);
            if(commutative && xmm_a > xmm_b) { int p=xmm_a;xmm_a=xmm_b;xmm_b=p; }
            EmitN(SSEop_regpair(op, result_reg = xmm_b, xmm_a), 4);
        }
        mathstack.resize(mathstack.size()-1);
        mathstack.back() = Where(Where::xmm_tag(), result_reg);
    }
    
    /* Ensures that the mathematical stack top is in XMM0 and ST(0) */
    void LoadResult(std::vector<unsigned char>& codebuf)
    {
        Where& what = mathstack.back();

        const int wanted_xmm_register = 0;
        int stack_offs = what.pos == Where::stack ? what.r : -999;
        if(what.pos == Where::xmm)
        {
            if(what.r != wanted_xmm_register)
            {
                // Move the result to the correct XMM register
                EmitN(SSEop_regpair(0x10, wanted_xmm_register, what.r), 4);
                what = Where(Where::xmm_tag(), wanted_xmm_register); // now it's there.
            }
        }
        else
            LoadInto(codebuf, what, wanted_xmm_register); // Load into the XMM register
        
        // Now the value is in XMM0. Spill it so we get a copy in the stack.
        // It will stay in XMM0, though. (Only spill if we don't have it in stack.)
        if(stack_offs < -128 || stack_offs > 127)
        {
            ForgetHwStack();
            Spill(codebuf, what, GetFreeHwStackSlot());
            stack_offs = what.r;
        }

        // Load a copy of the value to ST(0) just in case we have 387-based fpmath.
        static unsigned char FLDseq[3] =
            { sizeof(FloatType)==8 ? 0xDD : 0xD9, 0x45, 0};
        FLDseq[2] = stack_offs;
        Emit(FLDseq);
    }

    int getmax_hwstack_size() const { return max_hwstack_size; }
    int size() const { return mathstack.size(); }

private:    
    int Load(std::vector<unsigned char>& codebuf, Where& what, int avoid_gobbling)
    {
        if(what.pos == Where::xmm) return what.r;
        
        // Figure out if there's a free register
        int xmmregno = FindFreeRegister();
        
        // Spill one register if no free was available
        if(xmmregno == -1)
            xmmregno = SpillOldestRegister(codebuf, avoid_gobbling);
        
        // Load the stuff into the register
        LoadInto(codebuf, what, xmmregno);
        
        return xmmregno;
    }

    int FindFreeRegister() const
    {
        bool used[8] = {false}; // Figure out which registers are used
        for(size_t p=mathstack.size(); p-->0; )
            if(mathstack[p].pos == Where::xmm)
                used[mathstack[p].r] = true;
        for(size_t r=0; r<8; ++r)
            if(!used[r])
                return r; // This register is free.
        return -1; // No free registers
    }
    
    int SpillOldestRegister(std::vector<unsigned char>& codebuf, int avoid_gobbling)
    {
        for(size_t p=0; p<mathstack.size(); ++p)
        {
            if(mathstack[p].pos == Where::xmm)
            {
                if(mathstack[p].r == avoid_gobbling) continue; // can't use this
                
                int xmmregno = mathstack[p].r;
                Spill(codebuf, mathstack[p], GetFreeHwStackSlot());
                return xmmregno;
            }
        }
        return -1;
    }
    
    void Spill(std::vector<unsigned char>& codebuf, Where& what, int stackindex)
    {
        if(what.pos != Where::xmm) return;
        
        int bp_offs = (stackindex+1) * -sizeof(FloatType), xmmregno = what.r;
        
        // 0x11 = mov m,r
        EmitSSEMemOp(0x11, xmmregno, 5, bp_offs);
        what = Where(Where::stack_tag(), bp_offs);
    }
    
    void LoadInto(std::vector<unsigned char>& codebuf, Where& what, int xmmregno)
    {
        // 0x10 = mov r,m
        EmitSSEMemOpWith(0x10, xmmregno, what);
        what = Where(Where::xmm_tag(), xmmregno);
    }
    
    void ForgetHwStack()
    {
        for(size_t a=0; a<mathstack.size(); ++a)
            if(mathstack[a].pos == Where::stack)
                mathstack[a].pos = Where::immed; // dummy
    }
    int GetFreeHwStackSlot()
    {
        std::vector<bool> used;
        for(size_t a=0; a<mathstack.size(); ++a)
            if(mathstack[a].pos == Where::stack)
            {
                size_t offs = -1 - mathstack[a].r / (int)sizeof(FloatType);
                if(offs >= used.size()) used.resize(offs+1);
                used[offs] = true;
            }
        for(size_t a=0; a<used.size(); ++a)
            if(!used[a])
                return a;
        
        size_t result = used.size();
        if(result >= max_hwstack_size) max_hwstack_size = result+1;
        return result;
    }
};

int main()
{
    std::vector<unsigned char> codebuf;
    Stack stack;
    int n_consts = 0;
    static const unsigned char BeginSeq1[] = {0xC8}; // enter N
    static const unsigned char BeginSeq2[] = {0x00}; // ,0
    static const unsigned char EndSeq[]    = {0xC9,0xC3}; // leave;ret
    static const unsigned char ConstSeq1[] = {0x90,0x90,0x90,0x90,0x90,0xE8}; // call $+N (remember to add enough Nop to make it aligned properly)
    static const unsigned char ConstSeq2[] = {0x5A}; // pop dx (edx or rdx)
    Emit(BeginSeq1);
    Emit(BeginSeq2);
    Emit(ConstSeq1);
    Emit(ConstSeq2);
    std::string line;
    std::cout << "Enter RPN expression: ";
    std::getline(std::cin, line);
    std::cout << "\n";
    for(size_t a=0; a<line.size(); ++a)
    {
        int binop = 0;
        // Non-commutative operands, indicated by negative binop:
        if(line[a] == '-' && !isdigit(line[a+1])) binop=-0x5C; // subss/subsd
        else if(line[a] == '/') binop=-0x5E; // divss/divsd
        // Commutative operands, indicated by positive binop:
        else if(line[a] == '+') binop=0x58; // addss/addsd
        else if(line[a] == '*') binop=0x59; // mulss/mulsd
        // Check if we got an operand:
        if(binop)
        {
            if(stack.size() <= 1) goto error;
            stack.Binop(codebuf, binop<0?-binop:binop, binop>0);
            continue;
        }
        if(isspace(line[a])) continue;
        char* endptr;
        FloatType immed = std::strtod(&line[a],&endptr);
        if(endptr == &line[a]) goto error;
        a = (endptr - &line[0]) - 1;
        
        // Add the constant into the const array
        int dx_offs = n_consts++ * sizeof(immed);
        EmitValAt(sizeof(BeginSeq1) + sizeof(BeginSeq2) + sizeof(ConstSeq1) + dx_offs, immed);
        stack.Push( Where(Where::immed_tag(), dx_offs) );
    }
    if(stack.size() != 1) { error: std::cerr << "Error\n"; return -1; }
    
    stack.LoadResult(codebuf);

    // Complete the "enter N,0" opcode in the beginning:
    unsigned short stackmax = stack.getmax_hwstack_size() * sizeof(FloatType);
    EmitValAt(sizeof(BeginSeq1), stackmax);

    // Complete the "call $+N" opcode in the beginning:
    int constbuf_size = sizeof(FloatType) * n_consts;
    EmitValAt(sizeof(BeginSeq1)+sizeof(stackmax)+sizeof(BeginSeq2)
             +sizeof(ConstSeq1), constbuf_size);
    
    // Complete the code by adding the function exit mantra:
    Emit(EndSeq);
    
    /*fwrite(&codebuf[0], 1, codebuf.size(), stdout);
    fflush(stdout); //- Use this to "ndisasm" the output
    */

    /* Allocate a buffer where the JIT code will be stored to.
     * Align it on page boundary and set it executable. */
    char* buf = (char*)((((unsigned long)malloc(codebuf.size()+4096*2)) + 4095UL) &~ 4095UL);
    std::memcpy(buf, &codebuf[0], codebuf.size());

#ifndef _WIN32
    mprotect(buf, codebuf.size(), PROT_READ | PROT_EXEC);
#else
    VirtualProtect(buf, codebuf.size(), PAGE_EXECUTE_READ, NULL);
#endif
    FloatType (*buf_cast)() = (FloatType(*)())buf;
    /* Call the code */
    std::cerr << "result: " << buf_cast() << std::endl;
}
An example of the code it produces for the expression "1 2 3 4 * / 5 - 6 7 + 9 * 2 + + *" (64-bit):
enter 0x8,0
call $+0x48
dq 1.0, 2.0, 3.0, 4.0
dq 5.0, 6.0, 7.0, 9.0
dq 2.0
; the call lands here, having skipped 0x48 bytes (9*8)
pop rdx ; this receives the "calls"'s pushed immed table's begin address
movsd xmm0, [rdx+0x10]
mulsd xmm0, [rdx+0x18]
movsd xmm1, [rdx+0x8]
divsd xmm1, xmm0
subsd xmm1, [rdx+0x20]
movsd xmm0, [rdx+0x28]
addsd xmm0, [rdx+0x30]
mulsd xmm0, [rdx+0x38]
addsd xmm0, [rdx+0x40]
addsd xmm1, xmm0
mulsd xmm1, [rdx+0x0]
movsd xmm0, xmm1
movsd [rbp-0x8], xmm0
fld qword [rbp-0x8]
leave
ret
Post subject: Re: Self-modifying C code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Tub wrote:
If the address of s happens to equal a valid opcode that's used inside gruu(), it'll break.
Ah, true, true. Can't believe I forgot that.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Bisqwit wrote:
I'm not planning to use it for anything serious, though.
THIS, however does something serious :P It's a RPN calculator that uses JIT to produce fast-executing code :) It works on both Linux and Windows in both 32-bit and 64-bit modes. SSE support required, SSE2 utilized if available. It uses double-precision (double) arithmetics if SSE2 is available, single-precision otherwise. [code c++]#include <string> #include <iostream> #include <vector> #include <ctype.h> #include <cstdlib> #include <cstring> #ifndef _WIN32 # include <sys/mman.h> #else # include <windows.h> #endif #ifdef __SSE2__ typedef double F; #else typedef float F; #endif int main() { std::vector<unsigned char> codebuf; int stacksize = 0, stackmax = 0, n_consts = 0; enum { Q = sizeof(long) == 8 ? 0x48 : 0x90 }; // A prefix for 64-bit opcodes, or nop on 32-bit enum { S = sizeof(F) == 8 ? 0xF2 : 0xF3 }; // 0xF3 = movss/addss/etc, 0xF2 = movsd/addsd/etc static const unsigned char BeginSeq[] = {0x55,Q,0x89,0xe5,Q,0x81,0xed}; // push bp;mov bp,sp;sub bp,N (ebp or rbp, etc) static const unsigned char EndSeq[] = {sizeof(F)==8 ? 0xDD : 0xD9,0x45,sizeof(F), // fldq[bp+8] or fldd[bp+4] S,0x0F,0x10,0x45,sizeof(F),0x5D,0xC3};// movss xmm0,[bp+8];pop bp;ret static const unsigned char PushSeq[] = {S,0x0F,0x11,0x45,0x00,Q,0x81,0xed,sizeof(F),0,0,0}; // movss[bp],xmm0;sub bp,8 static const unsigned char ConstSeq1[] = {0xE8}; // call $+N (remember to add enough Nop to make it aligned properly) static const unsigned char ConstSeq2[] = {0x5A}; // pop dx (edx or rdx) static const unsigned char ConstLoad[] = {S,0x0F,0x10,0x82}; // movsd xmm0,[dx+N] static const unsigned char LdOne[] = {Q,0x81,0xC5,sizeof(F),0,0,0, // add bp,8 S,0x0F,0x11,0x45,256-sizeof(F)}; // mov xmm0,[bp-8] static const unsigned char LdPair[] = {Q,0x81,0xC5,sizeof(F)*2,0,0,0, // add bp,16 S,0x0F,0x10,0x45,0x00, // mov xmm0,[bp] S,0x0F,0x10,0x4D,256-sizeof(F)}; // mov xmm1,[bp-8] static const unsigned char AddSeq[] = {S,0x0F,0x58,0xC1}; // addss xmm0,xmm1 (or addsd) static const unsigned char SubSeq[] = {S,0x0F,0x5C,0xC1}; // subss xmm0,xmm1 (or subsd) static const unsigned char MulSeq[] = {S,0x0F,0x59,0xC1}; // mulss xmm0,xmm1 (or mulsd) static const unsigned char DivSeq[] = {S,0x0F,0x5E,0xC1}; // divss xmm0,xmm1 (or divsd) static const unsigned char NopSeq[] = {0x90}; // nop #define EmitValAt(offs, ptr) \ codebuf.insert(codebuf.begin()+(offs), \ (const unsigned char*)&(ptr), \ (const unsigned char*)&(ptr) + sizeof(ptr)) #define Emit(seq) EmitValAt(codebuf.size(), seq) Emit(BeginSeq); Emit(ConstSeq1); Emit(ConstSeq2); std::string line; std::cout << "Enter RPN expression: "; std::getline(std::cin, line); for(size_t a=0; a<line.size(); ++a) { #define binop(op) { if(stacksize<=1) goto error; \ Emit(LdPair);Emit(op);Emit(PushSeq); --stacksize; continue; } if(line[a] == '+') { binop(AddSeq) } if(line[a] == '-' && !isdigit(line[a+1])) { binop(SubSeq); } if(line[a] == '*') { binop(MulSeq); } if(line[a] == '/') { binop(DivSeq); } if(isspace(line[a])) continue; char* endptr; F immed = std::strtod(&line[a],&endptr); if(endptr == &line[a]) goto error; a = (endptr - &line[0]) - 1; // Add the constant into the const array int di_offs = n_consts * sizeof(F); EmitValAt(sizeof(BeginSeq) + sizeof(ConstSeq1) + di_offs, immed); n_consts += 1; Emit(ConstLoad); EmitValAt(codebuf.size(), di_offs); Emit(PushSeq); ++stacksize; if(stacksize > stackmax) stackmax = stacksize; } //fprintf(stderr, "stacksize = %d\n", stacksize); if(stacksize != 1) { error: std::cerr << "Error\n"; return -1; } stackmax = (stackmax + 1) * sizeof(F); EmitValAt(sizeof(BeginSeq), stackmax); int constbuf_size = sizeof(F) * n_consts; EmitValAt(sizeof(BeginSeq) + 4+sizeof(ConstSeq1), constbuf_size); Emit(EndSeq); /*fwrite(&codebuf[0], 1, codebuf.size(), stdout); fflush(stdout);*/ char* buf = (char*)((((unsigned long)malloc(codebuf.size()+4096*2)) + 4095UL) &~ 4095UL); std::memcpy(buf, &codebuf[0], codebuf.size()); #ifndef _WIN32 mprotect(buf, codebuf.size(), PROT_READ | PROT_EXEC); #else VirtualProtect(buf, codebuf.size(), PAGE_EXECUTE_READ, NULL); #endif F (*buf_cast)() = (F(*)())buf; std::cerr << "result: " << buf_cast() << std::endl; }[/code] It doesn't produce optimized code, though. Redundant memory stores abound. Also the way of using the call opcode for constants is a bit perverse, but at least it's position-independent. An example of the code it produces for the expression "7 4 5 * 2 - +" (64-bit): [code asm]push rbp mov rbp,rsp sub rbp,0x20 call $+0x20 dq 7.0, 4.0, 5.0, 2.0 ; the call lands here, having skipped 0x20 bytes (4*8) pop rdx ; this receives the "calls"'s pushed immed table's begin address movsd xmm0,[rdx+0x0] : movsd [rbp+0x0], xmm0 : sub rbp,8 ; push const movsd xmm0,[rdx+0x8] : movsd [rbp+0x0], xmm0 : sub rbp,8 ; push const movsd xmm0,[rdx+0x10] : movsd [rbp+0], xmm0 : sub rbp,8 ; push const add rbp, 0x10: movsd xmm0, [rbp+0x0] : movsd xmm1, [rbp-0x8] ; load two operands mulsd xmm0, xmm1 : movsd [rbp+0x0], xmm0 : sub rbp, 8 ; mul, store result movsd xmm0,[rdx+0x18] : movsd [rbp+0x0], xmm0 : sub rbp, 8 ; push const add rbp, 0x10: movsd xmm0, [rbp+0x0] : movsd xmm1, [rbp-0x8] ; load two operands subsd xmm0, xmm1 : movsd [rbp+0x0], xmm0 : sub rbp, 8 ; sub, store result add rbp, 0x10: movsd xmm0, [rbp+0x0] : movsd xmm1, [rbp-0x8] ; load two operands addsd xmm0, xmm1 : movsd [rbp+0x0], xmm0 : sub rbp, 8 ; add, store result fld qword [rbp+0x8] : movsd xmm0, [rbp+0x8] ; load function return value pop rbp ret[/code]
Post subject: Re: Self-modifying C code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Tub wrote:
s/works/depends on random factors/
Well, deterministic random factors. Those being the assumption that the two functions are placed adjacently in the code -- which they are when compiled with current versions of GCC or CL. (ICC untested) I'm not planning to use it for anything serious, though.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Didn't bother posting a new topic*, so I'll just add here. Self-modifying C code! It works on Linux and Windows. (32-bit only) Probably DOS too, if you remove the memory protection stuff. [code c]#include <stdio.h> #include <stdlib.h> #include <string.h> #ifndef _WIN32 # include <sys/mman.h> #else # include <windows.h> #endif static const char* const s = "gruu"; static const char* gruu(void) { return s; } int main(void) { const char*(*buf_cast)(); char *a = (char*)gruu, *b = (char*)main, *p, *buf; if(a>b) {p=a;a=b;b=p; } /* Note: For measuring the size of gruu(), we rely on the * assumption that gruu() and main() are placed adjacently * in the memory. But we don't know which way they are * ordered, so we swap those two if a>b, to ensure a<b. * That way, we can use (b-a) to calculate a rough estimate * of the size of the code within gruu() in bytes. */ /* Allocate enough memory and align it on page boundary */ buf = (char*)((((unsigned long)malloc(b-a+4096*2)) + 4095UL) &~ 4095UL); /* Create a copy of the function */ memcpy(buf, gruu, b-a); /* Change the function */ for(p=buf; p+sizeof(char*) <= buf+(b-a) ; ++p) { static const char* const h = "hello, world!"; if(*(const char**)p == s) { *(const char**)p = h; } /* case 1 */ if(*(const char***)p == &s) { *(const char*const **)p = &h; } /* case 2 */ } #ifndef _WIN32 mprotect(buf, 4096, PROT_READ | PROT_EXEC); /* Make it executable */ #else VirtualProtect(buf, 4096, PAGE_EXECUTE_READ, NULL); /* Make it executable */ #endif buf_cast = (const char*(*)()) buf; /* Cast the pointer into a function pointer */ printf("gruu() returns '%s'\n", gruu() ); printf("buf() returns '%s'\n", buf_cast() ); return printf("s is '%s'\n", s); }[/code] It creates a copy of the gruu() function, and modifies it to produce a different string. EDIT 2010-01-22: I just split the topic, so now it is a new topic after all. The old topic was the Hello, I suck at C:) thread.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
moozooh wrote:
One of the best examples of single-channel pseudo-polyphony is 1_channel_moog.it (explanatory video).
Neat tricks, though that's not real single-channel sound and consequently not "pseudo" polyphony. It uses the Instrument Mode, in which the sample played on the channel can sustain and continue playing on the background while a new one is started.* In Instrument Mode, the relationship between module channels and sound ("virtual") channels is relaxed. In traditional, MOD-style Sample Mode, the relationship is rigid: each module channel corresponds to a single sound channel. If this module used the "Note Cut" as the New Note Action rather than "Note Off", things would be different. *) That's roughly equivalent to holding down the sustain pedal and playing the piano single-fingered, and putting a little robot called Pitch Envelope inside the piano to stretch the released strings to produce different tones while the piano operator plays new keys. In olden times, "pseudo" polyphony was usually achieved with two methods: 1. Arpeggio, in which the pitch on the same channel is varied in rapid succession, e.g. between C,E,G,C,E,G 10 times in a second. For a profound example, listen to The Micro Machines title screen on NES. On even more restricted platforms such as the C64 or the Spectrum ZX, the arpeggio is interrupted by different instruments such as drumming. The listener won't easily realize that the chord and the percussion are played alternatingly on the very same channel. 2. Chord samples, i.e. samples in which several different sounds are recorded together as one sample to be played on one channel. Minor and major string chord samples were very common in MODs. For a NES example, see Gremlins or Journey to Silius. The effect is used in the DPCM sample. This is naturally only possible on platforms supporting recorded samples beyond basic waveforms.