gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > How to own yourself [SOLVED. Was: delete not doing it's job]

#161434 - silent_code - Sun Aug 03, 2008 1:33 pm

This is a fork from the topic over here.


This is used with C / C++ with gcc on the PC, but I guess it's not related to that. Anyways, I haven't tested this on the NDS, yet.


How come that calling delete won't work?


Example 1:

The button.

The following is the line used multiple times (exact copy and paste) to create an invisible, clickable button for an interface image. Everything works, besided, that it creates a memory leak.

Code:
      pGUITemp = new CGUI_Button(CTreeEditor::actQuit, vec2(16.0f, 8.0f), black, "");


The third of eight calls generates a memory leak in one program, although there is nothing special about it. All the instances of the button class get properly deleted in the exact same way by the exact same code.

Here's the code for creating and assigning the button to the GUI (proprietary system) hierarchy:

Code:
      pGUITemp = NULL;
      pGUITemp = new CGUI_Button(CTreeEditor::actQuit, vec2(16.0f, 8.0f), black, "");

      if(pGUITemp != NULL)
      {
         pGUITemp->setPos(vec2(176.0f, 32.0f));
         pGUITemp->setSize(vec2(32.0f, 32.0f));

         pGUI->addChild(pGUITemp);
      }


The only difference between instances is the position and I am pretty sure that that doesn't affect anything.


Example 2:

The memory leak tracer test case.

Code:
   // let's create some memory leaks!
   int *leak0 = new int[32]; // should not leak!
   int *leak1 = new int;
   int *leak2 = new int[2];

   memset(leak0, 0, sizeof(int) * 32);
   *leak1 = 1;
   memset(leak2, 2, sizeof(int) * 2);

   delete [] leak0;
   leak0 = NULL;


Now, leak0 won't leak, but if I move the definition / allocation line below leak2, it will!?
Why is that?


General info:

I'm on XP/SP3 (German) - same behaviour on other systems, so I don't think that's the "problem" - and I'm using gcc 3.4.5 with a debug build (no O, no profiling) with a custom memory leak tracer.

The tracer creates a list of allocations (made with a custom linked list, not std::list {for obvious reasons}) and has the standard new and delete operators overloaded, which will report "anonymous" leaks in addition to custom new and delete operators, which collect file and line information through preprocessor macros.

std::nothrow placement new is not being overloaded.
No new class operators are used.
All allocations and deallocations are internally handled with malloc and free and no exceptions are thrown (NULL is returned instead).
Sentinels and memory dumps are not supported, yet, but I will expand the system to include these features (it's easy to add to my existing codebase).


The applications terminate with a defined state (success, if I don't call Win32 message dialogs...)

It's all just standard stuff, I guess. ;^)


Can anybody help me? Maybe point me to some better, free memory leak tracer? Although mine does the job, there are these (I guess) rare and (definitely) weird cases, that just don't make sense (just like the poll!)
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.


Last edited by silent_code on Tue Aug 05, 2008 8:00 pm; edited 3 times in total

#161439 - keldon - Sun Aug 03, 2008 7:35 pm

silent_code wrote:
Now, leak0 won't leak, but if I move the definition / allocation line below leak2, it will!?
Why is that?

Without knowing more details, it just sounds like your leak tracer logic is faulty!

#161440 - PeterM - Sun Aug 03, 2008 8:16 pm

I probably don't understand this correctly, but are you deleting leak0, nulling leak0, then trying to memset the memory at leak0?

That would cause an access violation (writing to a null address).

BTW, no need to check if a pointer is null after calling "new" (unless you're using Visual C++ 6, yuck...). If the program gets to the code after the "new", it succeeded in allocating the memory. Otherwise it will have thrown a std::bad_alloc.
_________________
http://aaiiee.wordpress.com/

#161442 - silent_code - Sun Aug 03, 2008 10:39 pm

@ PeterM:

No and no. ;^D

1st No: I am allocatin, setting and then deallocating the memory leak0 points to. no further access is being made to the memory location that leak0 points to (that means to NULL, to which the pointer is set after deallocation.) There are no runtime errors either. As I stated, the programs using the tracer and exhibiting that strange behaviour will terminate in a defined state (= not crash.)
I guess that one is very clear. :^p

2nd No: As I have stated above, I overload / overwrite the new and delete operators to NOT throw any exceptions and instead return NULL in case of an allocation fault.

To my knowledge, exceptions are good for error detection, but I guess not using them is faster (I'm not sure about that one, though.)

But thanks for trying! :^)


@ keldon: But why would it work for seven buttons created in the exact same way and for one it wouldn't? It's beyond me. Also, the logic is very simple... pass an address from malloc() to a list, then in delete, look for the address in the list and remove the entry.


I do all my news and deletes right from the start. I double checked the code using the memory... nothing is wrong. It's just like in that second example (which is the more interesting one of the two.)


I'll investigate a little further tomorrow after work (when not playing BioShock... ;^D )


Hm, I could post the tracer here, after I test it on the NDS... would that help? (Yes?)


Has anyone else experienced such behaviour? Please share!
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161444 - keldon - Sun Aug 03, 2008 11:43 pm

I once had a weird problem with my "so blindingly simple it can't possibly go faulty linked list" structure was screwing up when sorting! I honestly couldn't see what could have been going wrong and only realized when I used a standard container - proving my logic to be faulty (which is obviously not an option for you due to standard containers using new/delete; unless you provided a custom allocator).

Are you displaying logs of when your code is called so that you have a clear idea of what your leak tracer is seeing (to be sure that new and delete are misbehaving)?

Your second example should not be giving any errors unless your new/delete are not getting called when you expect them to! I personally had no errors in changing the order - although I haven't installed GCC (just reinstalled Windows again and only have one dev env on).

#161448 - koryspansel - Mon Aug 04, 2008 1:30 am

Is it safe to say that this program, with your custom leak tracer, would leak memory on your system?:

Code:

int main(int, char*[])
{
    // let's create some memory leaks!
    int *leak1 = new int;
    int *leak2 = new int[2];
    int *leak0 = new int[32];

    memset(leak0, 0, sizeof(int) * 32);
    *leak1 = 1;
    memset(leak2, 2, sizeof(int) * 2);

    delete [] leak0;
    leak0 = NULL;

    delete [] leak1;
    leak1 = NULL;

    delete [] leak2;
    leak2 = NULL;

    return 0;
}


And for your custom leak tracer, you define both new & delete and array new & delete? Can you repro the problem using objects in place of the ints?

I would have to agree with keldon, you probably have a bug in your code somewhere. However, one thing you can do to check is replace the ints with an object and print a message in the constructor & destructor. It won't tell you much, but you can compare it to your leak tracer results to make sure your overloaded news & deletes are being called correctly and at the right times. Is there an equivalent for CrtDebug on GCC?

Wrt memory allocation & exceptions, I like to think about it this way. What does it mean if an exception is thrown or the result is NULL? And how is this condition going to be handled? Does it make sense to try and continue on if you have no more memory? And what if you check the result, and it is NULL? If you ignore that fact and continue, you've only delayed the problem and possibly made it harder to track down. Also, most of the cost of an exception, is probably in setting up the try/catch block, but it depends on a variety of factors. In addition, new may not be the only function that throws an exception, so you're not completely off the hook.

--
Kory

#161449 - sgeos - Mon Aug 04, 2008 2:09 am

Bugs involving pointers can be really confusing until you figure out what is going on.

Depending on exactly what you are doing, it may not be possible to test all of your code on the PC, but you should test as much of it as possible on the PC. Not only are the PC side testing tools better, there is a remote chance there is a bug in the implementation.

My second question is... if you are really leaking, why not try putting the offending code in a function and repeatedly call it until you run out of memory? Set your GBA/DS down, get lunch and see if it has crashed by the time you get back.

-Brendan

#161455 - tepples - Mon Aug 04, 2008 4:23 am

koryspansel wrote:
Wrt memory allocation & exceptions, I like to think about it this way. What does it mean if an exception is thrown or the result is NULL? And how is this condition going to be handled? Does it make sense to try and continue on if you have no more memory? And what if you check the result, and it is NULL?

The article "To New, Perchance to Throw" [1 | 2] explores the implications of operator new failure.

Quote:
Also, most of the cost of an exception, is probably in setting up the try/catch block, but it depends on a variety of factors.

Like devkitARM, MinGW uses static libstdc++ and static libsupc++. When I researched GNU libstdc++'s iostream bloat some time ago, I used ostringstream as an example. The sizes of the binaries compiled for MinGW (x86) and devkitARM (Thumb) were roughly comparable: about a quarter megabyte each. In one program I wrote using the Allegro library, I found that code and data directly related to exceptions were adding roughly 32 KiB to the binary. But I guess that amount of overhead is a bit less significant on the DS than in a GBA multiboot program.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#161460 - silent_code - Mon Aug 04, 2008 8:18 am

Hey, thanks for the replies, guys! (You've got yourself a cherry, Berry! / Nothing new, Jessy Blue.)

:^D


Oh, yes, btw: I use MinGW.

@ koryspansel: I will check it out later today (after work).

@ keldon: That's the point: How do I make sure delete gets called in exactly the place it's been called?

Well, before I overloaded the standard operators, I used std::list, with the same result! (EDIT: Not! It were other errors and leaks not detected by the last version of the tracer!)

The examples are a bit dumbed down to be to the point and easy, but yeah, it also happens with opjects (see CButton above). Also, I normally check every allocation etc.

Well, don't do uncatched exceptions result in runtime errors? I don't get any. I also use the C libraries most of the time (all of the time) and only use C++'s object systems for memory semi-"automatic" (with emphasis on the destructor) memory management. I don't think there's a whole lot of exceptions to catch with that, but it's a valid point.
Let's just assume I do take care of that. :^)

I will do some more testing and I'll also add sentinels and memory dumping and see where that leads to. If I don't find it then, I think I might post my tracer for inspection.

And I'll definitely read that article! Thanks tepples. :^)


Thanks guys! Keep it coming. :^)
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.


Last edited by silent_code on Tue Aug 05, 2008 7:44 pm; edited 1 time in total

#161471 - Miked0801 - Mon Aug 04, 2008 6:12 pm

Perchance, are you doing any new/delete stuff in an interrupt function? That's a sure fire way to get crazy leaks.

#161475 - silent_code - Mon Aug 04, 2008 7:20 pm

Interrupts on the PC under WinXP?
I hope that's only available in kernel mode and via ASM! :^D

Seriously, there is NOTHING exotic about any of my programs. All of them are single threated (except one, but that one doesn't count) - even easy ones exhibit the behaviour.

I'm on to some more testing, although I'm a bit exhausted...

"I'll be back."
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161484 - keldon - Mon Aug 04, 2008 9:30 pm

Regarding delete getting called, I was directing that statement to the possibility of a class being inherited without a virtual destructor.

Your example stands out a lot because not much is going on, so there has got to be a fundamental flaw somewhere along the line!

#161486 - koryspansel - Mon Aug 04, 2008 9:36 pm

Quote:

Like devkitARM, MinGW uses static libstdc++ and static libsupc++. When I researched GNU libstdc++'s iostream bloat some time ago, I used ostringstream as an example. The sizes of the binaries compiled for MinGW (x86) and devkitARM (Thumb) were roughly comparable: about a quarter megabyte each. In one program I wrote using the Allegro library, I found that code and data directly related to exceptions were adding roughly 32 KiB to the binary. But I guess that amount of overhead is a bit less significant on the DS than in a GBA multiboot program.


Very true, exceptions aren't free size-wise either. It was my understanding that exceptions could still be thrown even when disabled (just no valid unwind info), but based on a quick GCC test, this doesn't appear to be the case, it won't even let you compile. MSVC allows this, but just issues a bunch of warnings. I'm not exactly sure what happens on the DS, but, in my opinion, an unhandled exception should cause a reset (in final at least, but maybe not debug). And if you've run out of memory, this seems like a reasonable course of action.

Quote:

Well, before I overloaded the standard operators, I used std::list, with the same result!


This definitely makes me think that the bug is in your leak tracer. I think the best thing to do is assume, for now, that delete (and new) functions correctly. If delete works and std::list works (it better!), then that would leave the leak tracer. So, it may be a good idea to try another. Unfortunately, I don't know of any, but I'm sure Google does.

#161487 - silent_code - Mon Aug 04, 2008 9:59 pm

EDIT: Removed relic counter ("n") from s_removeWatch() and changed it's loop termination condition to check for NULL.


Basically, the list IS the tracer!

And how do you explain the change in the ORDER of allocations? Nothing else has an influence on the memory being leaked or not... that sure is strange.


The interface looks like that:

Code:
void *operator new(std::size_t size, const char *fileName, long lineNumber);
void *operator new[](std::size_t size, const char *fileName, long lineNumber);

#define DEBUG_MEMORY_NEW new(__FILE__, __LINE__)
#define new DEBUG_MEMORY_NEW


Here's some code excerpts from the "new" implementation:

Code:
void *operator new(std::size_t size, const char *fileName, long lineNumber)
{
   void *pointer = NULL;
   _DEBUG_POINTER((pointer = (void *)malloc(size)), "Out of memory!"); // this checks for NULL and prints an error message in case of an error
   s_debug_newWatch((unsigned long)pointer, size, fileName, lineNumber);
   return pointer;
}

void *operator new[](std::size_t size, const char *fileName, long lineNumber)
{
   return operator new(size, fileName, lineNumber);
}


// standard versions

void *operator new(std::size_t size) throw(std::bad_alloc)
{
   return operator new(size, "Unknown", -1);
}

void *operator new[](std::size_t size) throw(std::bad_alloc)
{
   return operator new(size, "Unknown", -1);
}


"delete" follows closely:

Code:
void operator delete(void *pointer, const char *fileName, long lineNumber)
{
   if(pointer == NULL)
   {
      return;
   }

   s_debug_deleteWatch((unsigned long)pointer, fileName, lineNumber);
   free(pointer);
}

void operator delete[](void *pointer, const char *fileName, long lineNumber)
{
   operator delete(pointer, fileName, lineNumber);
}

// only the standard versions are actually used!

void operator delete(void *pointer) throw()
{
   operator delete(pointer, "Unknown", -1);
}

void operator delete[](void *pointer) throw()
{
   operator delete(pointer, "Unknown", -1);
}



Beware, watches! (Extra corny joke!)

Code:
static void s_debug_newWatch(unsigned long address, std::size_t size, const char *fileName, long lineNumber)
{
   CAllocationInfo *info;

   _DEBUG_POINTER(info = new (std::nothrow) CAllocationInfo, "New Allocation Info failed!");
   info->address = address;
   info->line = lineNumber;
   info->size = size;

   strncpy(info->fileName, fileName, MAX_FILENAME_LENGTH);

   allocationList.insert(allocationList.pLast, info);
}

static void s_debug_deleteWatch(unsigned long address, const char *fileName, long lineNumber)
{
   if(allocationList.empty())
   {
      return;
   }

   for(CAllocationInfo *i = allocationList.pFirst; i != NULL; i = i->pNext)
   {
      if(i->address == address)
      {
         allocationList.remove(i);
         break;
      }
   }
}



That code, with either my own list or std::list behaves THE SAME. (EDIT: No, it doesn't! I mixed up some other bugs with this! <- Stupid!)

I will try to make a very small demo. (I won't, now what it's solved! Instead I will release it. ;^D )

And I BET (do not take literally) there is some stupid mistake there... (EDIT: Sure was! I kind of won... something. [Like working software, maybe?])
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.


Last edited by silent_code on Tue Aug 05, 2008 7:54 pm; edited 2 times in total

#161488 - DekuTree64 - Mon Aug 04, 2008 10:21 pm

Does the nothrow new in newWatch bypass your overloaded new operator? I guess it must, or you'd recurse forever...

I don't see the watch info ever being deleted though. Just the pointer being removed from the list. But that shouldn't be tracked, right? So that wouldn't show up as an entry left in the list at the end...

What is the purpose of the variable 'n' in the loop in deleteWatch? It seems to just get incremented every time through the loop, but never actually used.

And I assume allocationList.pLast is a special end entry, and not the last valid entry? Come to think of it, is the entire list exempt from tracking? Otherwise you'd get crazy recursion again, adding a node to the tracking info list every time you add a node to the tracking info list...
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#161490 - koryspansel - Mon Aug 04, 2008 10:32 pm

Have you tried replacing new (std::nothrow) with malloc? Maybe there is some mismatch between overloaded vs. default new/delete calls going on??

#161503 - silent_code - Tue Aug 05, 2008 8:00 am

@ DekuTree64:

Oh, yeah, "n" is a left-over from previous iterations. Thanks for pointing it out!

Your assumption is right, new std::nothrow is not being overloaded, so recursion cycles don't happen. It behaves like malloc(), returning NULL on an allocation error.

The allocation / watch info objects get deleted by removing them from the list.

pLast is the last valid entry in the list. It's just like std::list.end(). The new entry will be appended to that last one and become pLast. It works as expected.
The entire list is not being tracked, what makes the program not go crazy in a runtime error rampage. :^)


@ koryspansel: Yes, I have thought about it, but then, what would I gain? I had to manually call the constructor and destructor, afaIk, and I don't expect the behaviour to change. :^(
The problem is, that there are no default new / delete calls (except for new std::nothrow), so I don't see a mismatch. :^(
Even when deleting memory with my overloaded operators, that was allocated with new std::nothrow, should be allright. :^\


I appreciate the help, guys. Thanks.
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161505 - koryspansel - Tue Aug 05, 2008 10:36 am

Quote:

pLast is the last valid entry in the list. It's just like std::list.end()


end() points to the element AFTER the last :)

Change:
Code:

for(unsigned long n = 0; i != allocationList.pLast; i = i->pNext, n++)


to:
Code:

for(unsigned long n = 0; i != NULL; i = i->pNext, n++)


And get rid of the n :) Also, there's no need to manually call the constructor/destructor when overriding new/delete, the compiler takes care of that.

#161515 - silent_code - Tue Aug 05, 2008 6:30 pm

@ koryspansel:

You suggested to use malloc & free directly instead of new (std::nothrow) & delete, that's the case when the constructor and destructor would have to be called manually. (Is that assumption right?)

You are right, end() is the element after the last valid one. I put in my list implementation in a rush, so I replaced all references to end() with pLast... kind of silly, isn't it? :^D

Thanks for the remark!

I'm getting somewhere here. Although now I get crahes at program termination. I'll add more debug output to see what exactly is happening.


EDIT:

OK!

I have done some more logging and apparently this dumb line was the cause of the error:
Code:
for(CAllocationInfo *i = allocationList.pFirst; i != allocationList.pLast; i = i->pNext)


This one works correctly now:
Code:
for(CAllocationInfo *i = allocationList.pFirst; i != NULL; i = i->pNext)


All because of stupid refactoring. (refactoring != stupid)
That caused the pseudo random behaviour... what a noobish mistake. :^D


This case is closed.


THANK YOU VERY MUCH! :^)


PS: In exchange for your help and participation, I will release the tracer (only for newcomers, as the experienced programmers surely don't need my implementation ;^D ) once it's tested on the NDS. :^)
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161520 - koryspansel - Tue Aug 05, 2008 8:52 pm

Glad to hear you've got it all sorted out!

Quote:

You suggested to use malloc & free directly instead of new (std::nothrow) & delete, that's the case when the constructor and destructor would have to be called manually. (Is that assumption right?)


Well, no, since you're overriding new/delete in the first place the compiler will take care of it. Inside of new/delete you also don't have any information about the type of object being constructed, maybe it's an int and has no destructor? And actually, you can't even call the constructor. The trick is to use the placement new, and the most common use of this, I've seen, is to allocate memory from a pool:

Code:

void* pMem = allocateMemoryFromSomePool();
Resource* pRes = new (pMem) Resource;

...

pRes->~Resource();
freeMemoryFromSomePool(pRes);


Besides this, I can't think of a lot of reasons for ever needing to call the ctor/dtor manually.

--
Kory

#161522 - silent_code - Tue Aug 05, 2008 9:59 pm

I really don't get the idea of what you are suggesting...
Would you kindly elaborate? ;^)

My current new / delete call malloc / free internally and when used, constructors and destructors get called automatically. Placement new won't be overwritten. So far it's totally clear.

But what did you want to say with this?

koryspansel wrote:
Have you tried replacing new (std::nothrow) with malloc? Maybe there is some mismatch between overloaded vs. default new/delete calls going on??

Did you mean overloading the std::nothrow placement new in the same manner like the others?
Or did you mean to replace the calls to the operator with calls to malloc? (This is how I understood it.) That approach would require manual constructor calling.
Btw: I only use std::nothrow in places I don't what to be traced (like in the tracer itself).

As you probably see I'd like to understand what you want to tell me, but I'm confused. :^)
(Also, I'm debugging a multithreated application at the moment - which adds to the state of general confusion. ;^D )
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161525 - koryspansel - Tue Aug 05, 2008 11:01 pm

Quote:

Would you kindly elaborate?


Did I come across as being harsh? Apologies if so.

Quote:

My current new / delete call malloc / free internally and when used, constructors and destructors get called automatically. Placement new won't be overwritten. So far it's totally clear.


Sorry about that, didn't mean to add to the confusion. I was randomly thinking that maybe when you freed your CAllocationInfo objects there was some mismatch between which new/delete was used, but now that I look over the code again, that wouldn't be an issue anyway.

Quote:

Or did you mean to replace the calls to the operator with calls to malloc? (This is how I understood it.) That approach would require manual constructor calling.


No, I meant it only for the allocation info. I was mainly just throwing out ideas to check, grasping at straws if you will. But, yes if you replaced your global operator new/delete calls with malloc/free you would have to construct/destruct the objects manually. I guess I should have just said that if you call new/delete the compiler calls the ctor/dtor automatically.

#161526 - silent_code - Tue Aug 05, 2008 11:12 pm

Would you kindly play BioShock?

I hope you get the joke... like by now. :^D

Ok, then everything is clear.

I'm going to bed. :^D
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161528 - tepples - Tue Aug 05, 2008 11:23 pm

silent_code wrote:
Would you kindly play BioShock?

Would you kindly buy me a new PC, an Xbox 360, or a PS3? All I have are an old PC, a DS, a PS2, and a Wii.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#161535 - koryspansel - Wed Aug 06, 2008 1:13 am

Ahh I see...haven't played BioShock before, so that would explain it. Good to know :)

#161553 - silent_code - Wed Aug 06, 2008 7:56 am

@ tepples:

All I have is a bunch of (very) old laptops and an old desktop PC, a rather new and fast laptop (which plays Cysis ok), an NGC, a GBA, a SNES and an NDS... :^D
No money for a Wii atm. ;^)

I hear you americanos will get a 360 cut, so that the arcade model (no HDD) will cost "only" 200 USD.
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#161556 - kusma - Wed Aug 06, 2008 8:32 am

silent_code wrote:
I hear you americanos will get a 360 cut, so that the arcade model (no HDD) will cost "only" 200 USD.

Whut? That's like... 100?... Dead cheap! When will this cut happen?

#161573 - silent_code - Wed Aug 06, 2008 6:25 pm

It's scheduled for Sep. 7th.

Read more about it over here.
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.