The Long View

 

Fixing Canvas

 

   In 1990, I bought a copy of Canvas, version 3.01.  It wasn’t a subscription.  It was a license in perpetuity.  Nonetheless, a little while ago, when I tried to launch my copy of Canvas, it told me that Time had Expired, which it said had an Info/Location Code of 203/0, and it offered no probable causes or solutions.  When I said that was Ok, it quit.  I know I shouldn’t have agreed.  I knew that time had not really expired.  But there was no button available for registering a disagreement.  Canvas 3.01 claims that Time is Up, and that is that.  I would contact Deneba Software, in Miami Florida, to discuss it with them, except they don’t exist anymore.  According to Wikipedia, Deneba was acquired by ACD Systems of Victoria, B.C. Canada in 2003, and they have not published a Mac version of the program since 2005.  There was a total rewrite of the program in 1996 anyway, so probably there was nobody around there that knew what to do about my problem.  Probably they would have suggested that I buy a new version of the program.  But Canvas was totally ruined by the 1996 rewrite, and as a result  I dumped it and switched to Adobe Illustrator/Photoshop.  But I still have some old Canvas documents from back in the 1990’s, and I want to use them.  I want to run my old version of Canvas, not buy a new one. 


    What are my rights in this situation?  I don’t remember-- did I promise never to reverse engineer Canvas?  If I did, do I still have to honor that promise, given that Deneba Systems apparently planted a time bomb in a program that was sold to me under a permanent right-to-use?  And most importantly, have they not violated whatever agreement we had by saying something as stupid and inane as “Time Expired”?  Here I am, here it is, time is still going by, as always.  Whatever you might believe about the approach of the End of Time, I think we can all agree that it has not happened as of the time I am writing this.


I think it’s okay for me to fix this myself

    I’m not a lawyer, but on the basis of the above argument, I declare that I have a right to do whatever needs to be done to make Canvas 3.01 work again.   So here we go.


    The clue is the dialog it is displaying and that’s where I start.  In ResEdit, I go looking for a dialog box that looks like this, but I don’t find one.  I can find the string “Time Expired” in string 35 of  STR# 1015, but I doubt that will be all that useful.  So I’m going to just break into TMON while that dialog is up, and walk the stack backwards till I find the subroutine that is determining whether time has expired or not.  I start the program in mini vMac with TMON installed.  When the dialog comes up, I type control-i-y to enter TMON.  I open an assembly window and anchor it on the program counter (PC).
 
    I am in a toolbox call, SystemClick. Oh, of course.  There is a modal dialog box up, and so the executing code is in the toolbox, waiting for me to once again agree that Time has Expired.  Well, not this time buddy. I’m putting stop to this insanity.  So  I want to be in the subroutine that is calling this one.  For that I go to the User Area window and toggle pages by putting the cursor on that line and hit return, until I see the page that has Stack Crawl on it.  I put the cursor on that line and hit return, and I see that the caller is at address 3D17B4, 0D3E bytes into CODE resource #7.  So let’s look at that one.  Type 3D1788 into the Assembly window.
 
This shows that at 3D17B4, we are pointing at the return from Modal Dialog.  That seems right.  But even this isn’t the code I seek.  This is a routine that draws that dialog box.  I want the routine that figures out whether the dialog box should be drawn or not.  So I have to go one step back up the stack walk.  So I position the cursor at the end of the Stack Crawl line in the User window, and punch return again.
The stack crawl says that subroutine contains 37516C, in CODE resource E.  So let’s look there.  Ok, that looks better.  There are a some calculations and test, and finally there is a TST D4, that if it tests non-zero, is going to jump around the subroutine that draws the dialog box.  It looks like D4 contains the boolean that says whether everything is okay or not, and if it is zero, we get sent into the code that puts up the error dialog and then quits.  If it is non-zero, we are okay.  Gotta find out what determines the value of D4.   I’ll scroll way up and see where it first appears in the subroutine. D4 starts out by being assigned the value of 0, at location 37509E. So, if D4 doesn’t get set to some non-zero value before we get down to 37515C, we will have Expired Time.  Right after D4 is initialized to zero, a subroutine is called that takes a pointer to a local variable as a parameter.  I’m guessing that  It will probably initialize that variable.  TMON calls it $FFFA(A6).  I don’t know why TMON formats it that way.  I think it should be called -6(A6).  I know it is a local variable because of the negative offset from A6, the stack frame pointer.  I’m guessing it is going to return a value because it is passed by reference.  That is, it’s address is pushed on the stack (using PEA) instead of it’s value.   I’ll call this function  func1.  Upon return from that function, a resource is
read in.  The resource is type #746F6C79, which is hex for is ‘toly’ (TMON’s number window can do the translation).  There is a ‘toly’ resource number 03F3 (1011 decimal).  That resource gets read in, and its handle gets stored in A4.  Then the handle gets pushed onto the stack as the input to a series of 3 more subroutines, that I am calling func2, func3, and func4.. The first and third of these take only the handle as parameters, but the second takes the address of another local variable. This one, $FF88(A6), Is probably initialized in func3 (same logic as before). Later, the values of these two local variables will be compared, and the value of D4 will be set to 1 if the comparison fails.  This really looks like the thing we are looking for.  It looks like if I want a quick fix, I could just change the original line that says MOVEQ #$00,D4 to MOVEQ #$01,D4. Then it wouldn’t matter whether we later set D4 to one or not.  I tried that using Resourcerer1.25, and it worked great. I could quit now, because I know how to fix the program. 


    But now I’m curious about what happened.  Did Deneba really put a time bomb in my program, or was it just some kind of programming error that didn’t get caught for 14 years.  I’m going to conduct an investigation.


    To do it, I have to understand func1-4, and those two local variables that get compared. But none of those functions are in this segment. They are accessed via the jump table.  Now this is a place where MacNosy would would be helpful.  It could disassemble the entire program, and resolve all the addresses so we could look at each of those subroutines in turn, no problem.  But Canvas is a big program, and when I try to look at it in MacNosy, it crashes MacNosy.  I have to do it in TMON.

 

The Investigation

    The first function receives the address of the local loc1 on the stack.  This function is not in the current segment, so the JSR instruction that calls it passes its location in the Jump table.  A5 points to the jump table.  I can open the register window and can see the value of A5, and then add the offset 3E92. Or, in the Number window, I type RA5 + 3E92, to get the address of the subroutine.  This only works if the segment containing this function has been loaded into memory, but we know it has. 
It was just executed as a part of process of telling us that Time had Expired. So the resulting address is the location of this function’s entry in the jump table.  We go there, and we see a JMP instruction.  The location we see there  is the location in memory of the function func1.  So I open an assembly window at that location, and there is the function.  All it does is to read the time (in seconds since midnight on January 1, 1904) from Low Memory Global $020C, and return it in the local variable that was passed on the stack. That’s half of what we need.


    Next the code uses GetResource to read in a resource of type ‘toly’, with resource ID 1011.  That resource gets read in, and its handle gets stored in A4.  Then that handle is pushed onto the stack,and a second function, func2 gets called.  The same method described above reveals that func2 simply locks the handle. 


    To prepare the stack for func3, the next line,  MOVE.L (A4),-(A7) dereferences the handle and pushes the locked and dereferenced handle onto the stack.  This is now a pointer .  Then it pushes the address of a local variable with: PEA $FF88(A6) [I’d prefer to call it loc2 or -78(A6)].  Now func3, which is again accessed through the jump table.
  The value passed in on the stack (the pointer to our resource value) is put into A0, and passed to  Date2Secs.  This is a toolbox call documented on page 379 of the original Inside Macintosh, Volume II.  It receives a pointer to a date/time record in A0, and returns a long word in D0 that contains the same date and time in number of seconds since midnight, January 1, 1904.  This function then returns that result in the local variable whose address we pushed onto the stack. 


    Hmm.  So the ‘toly’ resource contains a date/time record.  A date/time record consists of 7 16 bit integers.  The first is a year since 1904, with 2040 as the maximum year possible.  The next is 1 - 12 for the month, then 1 - 31 for the day, then 0 - 23 for the hour, then 0 - 59 for minutes, 0 - 59 for second, and 1-7 for the day of week.  Let’s open the resource in ResEdit or Resourcerer and get the ‘toly’ resource’s value and see if we can make sense of it.  The value is '07D4 0008 001F 000C 001E 0000 0001'.  So the date recorded there will be 2004, August 31,12:30:00.   This may explain why my copy of Canvas isn’t working.  That date has passed.  But surely Deneba didn’t purposely program an expiration date of 2004 for Canvas 3.0, back in 1990.  Just to check,, I try changing the resource to a later date, by adjusting the contents of the ‘toly’ resource.  Changing the first integer to a bigger number, like 07F8 (2040, the last year supported by date/time records)  makes the program work.  Once again, I could stop here.  I now have two working solutions to the problem, but it’s starting to look like Deneba intended things to go wrong in 2004, so now I want to see what those comparisons are.


  The next function, func4, just unlocks the handle.  The next thing is a comparison.  The value of our ‘toly’ date in seconds is compared to the value held in local variable $FFFA(A6).  If it is less than today’s time, we jump just past the line that is setting D4 to 1 (and messing us up).   So sure enough this subroutine is comparing today’s date with an expiration date for the program, stored in the ‘toly’ resource.  This isn’t some kind of error, or sloppy programming.  The ‘toly’ resource was probably intended for a temporary trial license to give potentials customers a 30 day free trial license.  But what could be the point of making the program expire for regular customers on August 31, 2004?  Certainly this isn’t intended to force customers to update their version of Canvas.  The software update cycle was then and is now quite a bit shorter than 14 years.  Probably, the easiest way to give people a ‘permanent’ license was to put a long-distant date in the expiration date.  2004 was probably just picked as a long-distant time, or maybe the programmers even intended to use the longest-distant possible date in a date/time record (2040), and transposed the last two digits.,


The Verdict

    Deneba Software, of Miami Florida, you are hereby charged with the following crimes against your customers:


  1. A. Creation of a laughably stupid error message, “Time Expired”, and enclosing it in a badly designed dialog box with a meaningless error number and a place for Possible Cause and Solution, but then entering nothing there.

  2. B.Lazy and irresponsible programming, that caused a feature of your program that was intended for trial periods to be applied against programs running with a permanent license.  

  3. C.Violating your own license agreement by  terminating operation of your program for customers who have purchased a permanent license

  

Do you have anything to say in your defense? Uh-huh, I didn’t think so. You have been found guilty of all charges.  You are hereby sentenced to, within 6 years of committing these crimes, create a new version of your software that is so bad that your customers all immediately looking for another solution.  Over the following 5 years you will try to undo the damage, but fail.  In the end, you will be reduced to selling your crappy program to a few hapless Windows users who don’t know any better.

   Court is adjourned.


-- BG (basalgangster@macGUI.com)

 

Saturday, April 10, 2010

 
 
Made on a Mac

next >

< previous