pyehouse
7Aug/140

Weblogic, Kerberos, Single Sign On and You

We recently needed to implement single sign on in Weblogic for a client and finally managed to get it working, but had more difficulty than we probably ought to have. While some of our problems stemmed from our lack of experience with Weblogic, we also ran into problems with the documentation we were pointed to being vague, incomplete, and/or misleading. My goal with this post is to hopefully help others avoid some of the same misunderstandings and pitfalls we experienced.

First, I'll point you to three pertinent links which helped. Understand, though, that there were a number of other things we ran across through various searches that led to us working these issues out. The first two are official Oracle documents and the third was a scribd article that helped us put some pieces together.

How To Configure Browser-based SSO with Kerberos/SPNEGO and Oracle WebLogic Server

Configuring Single Sign-On with Microsoft Clients

Enable Kerberos With Workspace 11.1.1.3

As I said, the first two links are to Oracle documentation. I believe the second link is a bit more useful, to be honest, though the first does have a little sample servlet code you can slap into place for testing; but keep in mind that the instructions to make it work are incomplete. The useful bits on both have more to do with configuring Kerberos (e.g. account and SPN generation, keytab generation and installation) but leave a little out that would be very useful. The third article is a scribd article that describes exactly what the title indicates, but the setup for Kerberos was helpful in terms of getting the keytab right.

The first problem we had was with the account and keytab generation. We were using the first Oracle link which was a little more spartan with its explanation of how to set up the accounts in Active Directory and set up the keytab file. Sure, it has images and explanations but the selection of account and machine names is ridiculously confusing. The second Oracle link has a better explanation of the account setup and keytab generation but still includes some information that is either unnecessary or misleading.

  1. In the Active Directory create a user account which will be doing 'Kerberos things'. e.g. svc_someapp_krb
    1. By all means, ignore the suggestion of using the name of the client machine for the account name. Adhere to whatever naming convention makes the most sense. This one suggestion led to a lot of confusion while trying to decide how to configure the 'ktpass' command.
    2. 'ktpass' will create an SPN for you, but you can also do it yourself. The instructions suggest creating the SPN separately. It matters little.
    3. Keep in mind what encryption type to select in AD for the user. It must be consistent. We used AES 128. If you go higher than this remember you'll need to download Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files here and install them properly.
    4. Make note of the fact that changing the encryption type AFTER setting your password can corrupt the encrypted password. Reset your password (even if to the current value) if you change encryption.
    5. Sometimes Java implementations for keytabs have problems with special characters. What is a special character? We wound up using strictly alphanumerics to avoid the problem. We couldn't find a firm definition of what was allowed. You could try underscores and hyphens. Much more than that and you begin risking running into this issue.
  2. For our 'ktpass' our principal was of the form 'HTTP/ourserver.some.domain.com@SOME.DOMAIN.COM'. Notice the caps? You'll need that. Notice the repetition of the domain? You'll need that to. So our 'ktpass' was like:
    1. ktpass /princ HTTP/weblogicserver.domain.com@DOMAIN.COM /pass domainuserpassword  /mapuser domainuser@domain.com /ptype KRB5_NT_PRINCIPAL /out weblogic.keytab /crypto AES128-SHA1 /kvno 0
  3. If you are not using node manager, but are running your Weblogic server as a Windows service, you presumably already set up your install.cmd file. You'll need to add the specified options as JAVA_OPTIONS here. Remember, the Server->Server Start arguments are only useful when the Node Manager is handling things on a remote server. For local setup you'll need to use different means. We ended up using full paths for our various files.
  4. If you don't have access to the C:\Windows\ folder, you'll need to create your krb5.ini file and place it somewhere else, then add a startup option to point to it as the default location can't be used. You can do this via the following additional JAVA_OPTIONS parameter:
    1. -Djava.security.krb5.conf=C:\OurApp\krbstuff\krb5.ini

Keeping these points in mind while setting up things for Kerberos will help you avoid some of the issues we ran into. At this point we were able to add the NegotiateIdentityProvider to our security realm. The Kerberos debug settings allowed us to see that the ticket setup was working, plus kinit confirmed things too. But the test servlet still wasn't working.

So what went wrong next? For starters, our inexperience with Weblogic led to a lack of understanding of how the authentication system works. Let's take a look at that sample servlet in the first link. The weblogic.xml snippet (well, it's complete but tiny for sample purposes) looks like this:

<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN" "http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">
<weblogic-web-app>
  <security-role-assignment>
    <role-name>negotiateAdminRole</role-name>
    <principal-name>negotiateAdmin</principal-name>
    <principal-name>Administrators</principal-name>
  </security-role-assignment>
</weblogic-web-app>

If you understand this part, please skip it. This is just saying that "if something in the Weblogic security layer declares that the current visitor is a 'negotiateAdmin' thing or an 'Administrators' thing, then for this servlet we will treat it is a 'negotiateAdminRole' thing". I'm dumbing it down a bit, but given our inexperience with Weblogic when we were setting things up, we didn't yet know how Weblogic would determine that the current user was a 'negotiateAdmin' or an 'Administrators' member. We had the notion of an LDAP or AD (via LDAP) lookup but nothing had yet crystallized.

Now for a snippet from the sample web.xml:

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>BasicAuthSimpleTestServlet</web-resource-name>
            <url-pattern>/*</url-pattern>
            <url-pattern>/</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>negotiateAdminRole</role-name>
        </auth-constraint>
        <user-data-constraint>
            <description>no description</description>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <security-role>
        <role-name>negotiateAdminRole</role-name>
    </security-role>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>default</realm-name>
    </login-config>

Starting from the bottom and going up, check out the auth-method. BASIC is going to trigger a login prompt. We want single sign on and this page is meant to be an example of such using Kerberos. You want CLIENT-CERT here, not BASIC. CLIENT-CERT says "hey, browser, you are GOING to give me a certificate of some kind, including possibly a Kerberos ticket, or you will be denied". Quite simple. I suppose the sample was intended to give a visual cue but typically a browser would be configured to do that anyway if an 'Authorization: Negotiate' header came back from the server so again I'm not sure why this change was left in. Anyhow...

Moving up, we declare the existence of a security-role called 'negotiateAdminRole'. This makes it available for reference elsewhere, in this case in the auth-constraints tag agove it. If you take a look in the security-constraint you get the gist of what is going on; the servlet is being set up with its available access methods and is constrained to only those users with the negotiateAdminRole assigned. Now, recall the web.xml where we indicated who would be granted the negotiateAdminRole? That made some sense to us, but what was missing was the following:

The mapping of principal-name to role-name in web.xml is based on mapping whatever identity is retrieved from authentication providers to retrieved roles from various role providers via a simple string match.

Let's jump ahead a bit. Because of the NegotiateIdentityProvider in our security realm, Weblogic knew what our account name was due to the Kerberos ticket authentication taking place. So user 'jsmith' comes along and now the current user is identified as user 'jsmith'. But 'jsmith' doesn't belong to any roles and gets no principals assigned. Weblogic will take the user account and check with other providers to see if they know who 'jsmith' is and see if it has any group/role memberships.

It turns out that to make the sample servlet work, you can simply add a user to the built in Weblogic Users/Groups with the same account name as an account from the Kerberos domain, in this case 'jsmith'. Now add 'jsmith' to a Weblogic group as used in the sample servlet, e.g. 'Administrators'. That will immediately make the sample servlet start working assuming you are 'jsmith'. Moreover, it means that 'jsmith' will become an Administrator when hitting the Weblogic console so you now have single sign on working for your console.

But now the next part comes in, linking to your Active Directory implementation for LDAP lookups. Just as you added the NegotiateIdentityProvider, you can also add an ActiveDirectoryAuthentication provider as well as LDAPAuthentication. We read a number of articles suggesting that even if you are targeting Active Directory, you may still want to use LDAPAuthenticator to work around some problems. We in fact tried LDAP first but ended up getting ActiveDirectoryAuthenticator to work for us. The trick boils down to what you use for the Principal name and the various filters. You need an account you can log in with, preferably a service account of some kind. Some suggested you could log in with the Principal set to just the account name. We ended up having to use the full distinguished name. As for your filters, you'll have to consult with your AD admin, or be capable of examining your LDAP structure to draw your own conclusions. Keep in mind that if you don't fill in the fields for the 'All Users' and 'All Groups' filters, then the Users/Groups tabs will not be auto populated with the users/groups from AD.

Once you have this hooked up, you can start referencing groups that your Active Directory users are members of for security purposes. None of the actual steps in the process are necessarily difficult. What made this difficult for us was more to do with lack of complete and cogent documentation. This post isn't sufficient on its own to get you where you need to be, but hopefully will help clarify some of what you will read on other pages. Good luck!

Filed under: development No Comments
1Dec/130

Have You Seen This SQL Server Bug?

I may have found a bug regarding how SQL Server calls CLR functions when using columns from tables joined using non-indexed columns. I'm including a SQL script, with comments on what to do with it, and hopefully anyone reading this will try it for themselves and tell me if what I'm seeing happens for them and whether I'm misinterpreting things. I'm willing to admit if I'm misunderstanding how things are supposed to work but so far everyone that I have spoken with about this is as perplexed as I am. And yet none of us can imagine this has never been noticed before and also is working as intended.

For starters, I discovered this in 64 bit SQL Server 2008 R2. One of my coworkers, our DBA, also reproduced this on 64 bit SQL Server 2012. I posted the issue at stackoverflow but no one even commented on it. My summary of the problem as I posted there is as follows:

SQL Server (at least on 2008 R2, 64 bit), when processing a query that includes a CLR function call in the SELECT clause and when using at least one column from a table that is included via JOIN on non-indexed columns, appears to invoke that CLR function for every row not constrained by either an indexed column in the JOIN or by an explicit WHERE clause item.

Now for some more details. This all has to do with the Microsoft spatial objects CLR functionality. Suffice to say I have a table full of extents, latitude/longitude pairs marking the opposing corners of a region. Additionally I have an SRID (spatial reference ID) to indicate what coordinate system was used. Some use meters offset, some use degrees. Also, the data I have is dirty. It has NULL values, invalid SRID values, coordinates that don't match the SRID (i.e. meter offsets recorded for a supposedly degree based SRID). Finally, not only did I need to filter out potential offending values I also had additional filters being joined in from other tables. So my original SQL was actually doing a pre-filter pass to generate a valid set of ID values followed by the actual SELECT which joined this list of pre-filtered keys in a table variable with the original data table.

Part of the SELECT was to make use of the geography::STGeomFromText() call to return a geography object as a column in the result set. No sweat, my tests showed only valid rows were returning. But when I added that call, I began getting exceptions from the underlying CLR complaining about invalid geographies, invalid SRID values, insufficient numbers of points in the ring, etc. After a considerable amount of debugging and testing, I came up with the script listed below.

The first chunk of the script is commented out and includes two sections. One section creates a FUNCTION that takes a VARCHAR and an INT and just passes them directly to geography::STGeomFromText(). It is a simple wrapper. More on that in a bit.

The second section in the commented out area sets up the data table. The data table happens to include 7 test records but could probably work with fewer. I hit the exact results I was hunting down after I had added the 7th record and as much out of superstitious need to cling to my voodoo doll as anything else, I did not alter the data set. In any case, the CREATE TABLE is most notable for NOT indexing the ID column. The column is also not an IDENTITY but honestly making it an IDENTITY column did not impact the outcome and just added needless complexity.

The uncommented code that follows declares a table variable to hold valid keys, populates the table variable, and then performs a SELECT, returning not only all columns from the original data table but also additional columns which directly invoke the CLR function as well as call the UDF created up top. There is also a commented out WHERE clause.

I'll summarize what I saw before I dump the script.

  • If I do not include the CLR call as a column in the SELECT, the SELECT works and I only see records which have valid values that would work in a call to the CLR function.
  • If I call the UDF, a wrapper around the CLR, I again only see the expected records and I get valid geography objects returned for each.
  • If I call the CLR, I receive an exception about an invalid SRID (being NULL). Depending on the results of the full table scan, you may also receive errors about invalid geography point values. Regardless, the query fails.
  • If I uncomment the WHERE clause, I can now include the CLR call as a column with no errors.
  • If the WHERE clause is commented out but the original data table is instead recreated such that the id field is a PRIMARY KEY field (or really, indexed in any way but the obvious approach would be to make it a PRIMARY KEY), the CLR function may also be included in the SELECT without issue.

What I believe is happening is that the main query is joining two tables, the data table and the table with a simple list of keys. It joins these tables on the ID column. When there is an INDEX on the data table's ID column, SQL Server first joins on the results of the INDEX lookup, then fetches only the rows corresponding to the joined keys. Since the key table only has keys to rows with valid data, no invalid data is ever even seen. But when there is no INDEX, a table scan must be performed on the data table. While it is true that the result set will only end up including rows with valid data since that's the only keys in the key table, it seems as though the CLR function is still being called for each row in the scan, regardless of whether it will end up in the final result set.

The interesting thing about the UDF is that it seems to mask whatever behavior is going on with the CLR function call. That is, the UDF does not get called for every row of a table scan, only for the rows which would be returned in the result set.

Aside from simple stating the obvious, that the lack of an INDEX forces a full table scan and the database is giving the CLR function a shot on each row before it is filtered out by the JOIN restriction (though, oddly enough, after any WHERE clause), I can't see why this would happen. Wrapping it in a UDF seems to use a slightly different logic path. Perhaps it is a bug in the CLR assembly from Microsoft (this is Microsoft's geography implementation) but I don't have any other assemblies to test and our environment doesn't allow us to install anything else.

Anyhow, here's the script to reproduce the problem.

/*
 * This part of the script makes a UDF available to wrap the CLR call
 *
 * Only run this once
--
--
--
-- BEGIN RUN ONCE
--
--
--
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fLPPTestMakeGeography]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[fMakeGeography]
GO
CREATE FUNCTION [dbo].[fLPPTestMakeGeography]
(
    @p1 varchar(max)
    , @p2 int
)
RETURNS geography
AS
BEGIN
    DECLARE @Result geography
    -- just a wrapper around the geography::STGeomFromText call
    SELECT @Result = geography::STGeomFromText(@p1,@p2)
    RETURN @Result
END
GO
--
--
--
-- END RUN ONCE
--
--
--
*/
/*
 * Likewise this sets up the test table.
 * Note that this all goes away when a PRIMARY KEY is placed on the id column
 * Commentary: by making id a PRIMARY KEY, it causes the join in the main query
 * to do an index lookup rather than doing a full scan of the data table. Without
 * that PRIMARY KEY setting, even though the table variable is joined to the data
 * table in such a way as to eliminate any potential invalid rows, every row of
 * the data table gets examined. The fact that the CLR call throws an exception
 * in this case but the UDF call does not suggests the following:
 * For each row of a table that is not filtered in the JOIN clause via an index
 * and not filtered in the WHERE clause directly, any CLR call in the SELECT
 * clause will be called if it involves any columns from that row.
 * Note that this behavior differs from that of UDFs, where the UDF call only
 * takes place once *all* filtering is done, including non-INDEXed portions of
 * any JOIN statements.
--
--
--
-- BEGIN RUN ONCE
--
--
--
drop table dbo.LPPTestEnvelope
create table dbo.LPPTestEnvelope
(
id int not null
, geog int
, minx decimal(38,16)
, maxx decimal(38,16)
, miny decimal(38,16)
, maxy decimal(38,16)
)
insert into dbo.LPPTestEnvelope (id, minx, maxx, miny, maxy, geog)
select 1,-80,-70,20,30,4326 union all
select 2,-80,-80,20,20,NULL union all
select 3,-80,-75,20,25,4326 union all
select 4,NULL,NULL,NULL,NULL,4326 union all
select 5,-85,-70,25,40,4326 union all
select 6,NULL,NULL,NULL,NULL,4326 union all
select 7,-100000,-90000,3000000,3100000,4326
--
--
--
-- END RUN ONCE
--
--
--
*/
-- select * from dbo.LPPTestEnvelope
set nocount on;
-- in a more complex setting this table variable
-- would be used to do some complex pre-queries and hold the
-- key values which would be joined to the original data
-- table to get the actual rows desired
--
-- for this example it's just dumbed down but the concept holds
declare @envtbl table
(
    eid int not null primary key
)
INSERT INTO @envtbl (eid)
SELECT
    id
FROM
    dbo.LPPTestEnvelope env
WHERE
    -- exclude invalid SRID values
    env.geog=4326
    -- exclude invalid lat/long values (e.g. UTM values with improper SRID assignments)
    AND env.minx > -180 AND env.minx < 180 AND env.maxx > -180 AND env.maxx < 180
    AND env.miny > -90 AND env.miny < 90 AND env.maxy > -90 AND env.maxy < 90
    -- avoid precision bug
    AND ABS(env.maxx-env.minx)>1E-7 AND ABS(env.maxy-env.miny)>1E-7
    -- avoid crossing hemispheres
    AND SIGN(env.maxx)=SIGN(env.minx) AND SIGN(env.maxy)=SIGN(env.miny)
SELECT
    -- display the raw data from the table
    env.*
    -- directly invoke the CLR to create a geography object
    -- NOTE: COMMENT THIS COLUMN OUT TO MAKE THE QUERY WORK
    ,geography::STGeomFromText('POLYGON(('
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR)
            + '))', env.geog)
    ,[dbo].[fLPPTestMakeGeography]('POLYGON(('
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR)
            + '))',env.geog)
    ,'SELECT geography::STPolyFromText(''POLYGON(('
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.miny AS VARCHAR) + ', '
            + CAST(env.maxx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR) + ', '
            + CAST(env.minx AS VARCHAR) + ' ' + CAST (env.maxy AS VARCHAR)
            + '))'',' + CAST(env.geog AS VARCHAR) + ')'
FROM
    -- join our data table to our table variable to fetch only the desired rows
    dbo.LPPTestEnvelope env
    INNER JOIN @envtbl et on et.eid=env.id
-- NOTE: UNCOMMENT THE WHERE CLAUSE TO MAKE THE QUERY WORK REGARDLESS OF INDEX
--WHERE
--  -- exclude invalid SRID values
--  env.geog=4326
--  -- exclude invalid lat/long values (e.g. UTM values with improper SRID assignments)
--  AND env.minx > -180 AND env.minx < 180 AND env.maxx > -180 AND env.maxx < 180
--  AND env.miny > -90 AND env.miny < 90 AND env.maxy > -90 AND env.maxy < 90
--  -- avoid precision bug
--  AND ABS(env.maxx-env.minx)>1E-7 AND ABS(env.maxy-env.miny)>1E-7
--  -- avoid crossing hemispheres
--  AND SIGN(env.maxx)=SIGN(env.minx) AND SIGN(env.maxy)=SIGN(env.miny)
Filed under: development No Comments
17May/130

Stalemate Updates Coming Soon

I've recently been revisiting my little iOS chess like app, Stalemate. The latest update is currently in review and includes two major revisions. The first and most important is the addition of a tutorial mode. That is a feature that has been requested and honestly should have been there from the word go. When I put Centripetal together, the game play was simple enough that a simple help screen was sufficient with some explanation of the goal and obstacles. With Stalemate the scoring needed to be explained, the fact that pieces don't move once placed was probably not what folks expected and though simple it is still more complex than Centripetal. So yeah, should have made a tutorial to begin with.

The second and less important but more visible change is with the look and feel. I moved away from my attempt at aged parchment with some sort of Renaissance gradient font (seriously, what was I thinking?) to a simple black and white glossy look more in line with the icon set I used for the pieces in the first place. Here's the new splash page:

Splash screen for Stalemate iPhone app

Splash screen for Stalemate iPhone app

Okay, yes, I've picked another custom font. But this time there is no gradient at least. When I created the reflection, at first I just made it a normal reflection but then it occurred to me to reflect (see what I did there?) the oppositional nature of the game and therefore made the reflected portion a negative of the full portion. I may not have a future as a graphic design artist but I thought it was a nice touch.

Update - We're now live with the new look. In addition, I've made Stalemate and Centripetal free. I think I would rather see them getting played than continue to see a trickle of income off of them. Have fun!

Filed under: development No Comments
17Jan/130

Centripetal Re-released!

Greetings and salutations and welcome to 2013! For my first new post of the year I am very happy to announce that I have re-released Centripetal onto the iTunes App Store under my own name. 🙂 Click here to purchase Centripetal on the iTunes App Store.

As an aside, a fair bit has changed since I first released Centripetal. I had used cocos2d 0.99 at the time. XCode 3 was all the rage. iOS was at v4.x and OpenGL ES 1.0 was the standard.

Now cocos2d 2.0 is out, XCode is at 4.5, iOS is up to v6.x and OpenGL ES 2.0 is en vogue on our favorite mobile platform. As a result, it wasn't enough to just recompile and resubmit. I ended up upgrading cocos2d, box2d, CocosDenshion and the other related libs. I also needed to tweak some of the code that I happened to have which made use of the GL_POINTS_SMOOTH parameter in the call to glEnable. By tweak I mean remove. You'll see that on the help screens where the dot indicator showing which page you are on is now a square and not a circle. Yay progress?!

I do, however, want to also release this for OS X on the Mac App Store as well. Oddly enough I discovered it is enjoyable to play on the desktop due to running it in the iOS simulator.

Anyhow, if you already have your original copy, know that this is purely a re-release. If you haven't tried it out before, you can check out my Centripetal page here (or click the link at the top of the page in the header) and watch the gameplay video.

Filed under: development No Comments
4Sep/122

cscreen for Intel

Years ago (2004?) I wrote a little command line utility that allowed users to alter their screen resolutions, bit depths and refresh rates. To be entirely honest I don't even remember why I did it in the first place. However it seems to have been somewhat useful to a handful of folks over the years. I still see requests for it and occasionally get emails asking if I've updated cscreen for the Intel machines. The answer, now, is yes. I even created a page for cscreen for OS X so folks can link to it as needed.

That said... I do not have a Retina display and do not know anyone with a Retina display, so I cannot test it on such devices. If you have a Retina display and wish to give it a whirl, be my guest. In fact, if anyone wants to provide any feedback, please don't hesitate.

Enjoy!

Filed under: development 2 Comments
24Aug/122

Unity Hinge Joint Tutorial Project

In the process of getting started on Centripetal 2, I ended up needing to make use of Hinge Joints. I've put together a Hinge Tutorial Project project with a sequence of simple scenes that might help explain and demonstrate the various options associated with Hinge Joints. Enjoy!

23Aug/121

Centripetal 2

As some of you are aware, I created a little game called Centripetal and had it up on the iPhone/iPad app store. Wait, don't go trying to download it now. My former company, Phoenix Networking Group, who had rights to the game, has transferred those rights over to me. As a result, the game has been taken off of the app store.

"So what's this about Centripetal 2, then?" you ask. Well put. I'm rebuilding Centripetal.

For starters, I'm going to be using the Unity Engine this time around. When I first developed Centripetal, I made use of cocos2d for iPhone for sprite management and object handling and box2d for the physics. I was focusing on iPhone (and eventually iPad) development so the fact that this arrangement tied me to these platforms wasn't a showstopper. And to be clear they are very useful frameworks. But it did mean that some of my friends who do not use iOS devices were unable to play Centripetal.

I had discovered the Unity Engine some time ago and grabbed one of their free copies to tinker with but never really got into it. Recently I took the plunge, grabbed a tutorial, went through it and came out the other side not only impressed with Unity but also confident that I could make good use of it myself. I created a really simple game to become more comfortable with the editor, which I'll have to post about some time, and then started working on concepts for other games I might pursue. In the back of my mind, I wondered about converting Centripetal but for a number of reasons I put that aside.

That has changed though. I've started rebuilding it for Unity, with the goal of making it available for more folks to play (yes Kris, I'm hoping you and the wife and kids will finally get a turn :). I also want to add more to it. Inasmuch as Centripetal represents my first polished and completed game, I know there was more that could have been done. I hope to do that with Centripetal 2.

I don't imagine the new revision will make much more money, if any, than its predecessor but that's not really the point. I want to make something fun and keep building on something that began as my own creative endeavour. I'm glad I'm getting that chance.

Filed under: development 1 Comment
19Aug/120

Animation and Modeling are Non-Trivial

I want to tip my hat to those of you who have taken the time to learn modeling and animation. As a game developer (I suppose I get to call myself that by now) with an emphasis on the coding side of things, I have tended to try to "make do" when it came to visuals and sounds. When I finally splurged and involved an actual artist for the 2-d artwork, it was like night and day. I've always known at an abstract level that visuals and music are very important to a successful game. But until recently, I didn't realize just how much goes into the modeling and animation for 3-d assets.

Some background: thus far, my games have been 2-d, the most recent involving use of cocos2d, an excellent 2-d game framework which I highly recommend. My games have been limited to iPhone development thus far, thus the link to the iPhone specific port of cocos2d. There are others. In any event, they have primarily been sprite based and honestly not terribly complex.

I have known about Unity for some time now, having downloaded and played with it a few times in the past but never really "getting" it. This was before I had tackled cocos2d, so perhaps at the time I felt daunted by jumping straight into 3-d game development. Maybe the concepts didn't have time to settle. Whatever it was, it wasn't until this most recent bout with Unity that it finally clicked. As a result, I've now created a game (which I haven't yet released) with Unity and am laying the groundwork for another.

The first game is a simple arcade style game; one of those little time wasters you play when you are standing in line at the grocery store. It was sufficient to use the simple spheres, boxes and cylinders which you can create directly within Unity. The next game is a bit more ambitious and part of the reason is to do with the fact that I plan to use actual models imported for various objects. I want the visuals to take a step up. However, I don't know anyone with the modeling skills, the time and the inclination to create the models I'm looking for. At least not right now. I knew that Unity can import models from Blender, a free (and powerful) modeling application, however I lacked the knowledge of how to make Blender work for me. Still, I decided that what I could do is learn enough about Blender to create some simple prototype models, import those into Unity and do my development and put off finding someone to make proper models to a later date.

I'm coming to the conclusion here, bear with me.

I've since been going through tutorials on Blender. Honestly, I suspect I'm already at the point where I could create the simple prototypes I need to get started on working further on my game concept, but I plan to finish the tutorials out. What I'm finding is that modeling is more approachable than I first feared and more complex than I first imagined. Much like drafting, once you learn the basics of how to accomplish certain tasks (creating a solid from a series of points on a Bezier curve, concepts of box modeling, etc.), there is as much science as there is art to building your model. To be sure, some things will require an artist's appreciation to achieve certain effects, but getting from point A to point B isn't quite the boogeyman I first expected it to be.

At the same time, modeling and animation are also more complex than I first thought they would be. I'm not sure what exactly it is that I expected, but seeing how different parts interact, seeing how one minor change in dimension can adversely affect an entire model, how losing track of one key frame in an animation sequence can suddenly cause a smooth animation to break all known laws of physics and require you to start over... there is a lot more going on behind the scenes.

While I never dismissed modeling and animation as being simple, I think in some ways I gave them short shrift, thinking that the amount of time and effort put into such things wouldn't likely be on the same level as, say, the time spent coding the game. Well, no more. This one's for you Mr. (or Miss) Modeling-and-Animation-Guy (or Gal)!

Filed under: development No Comments
19Jan/120

Data Access with .NET Framework 4 (Exam 70-516)

I passed my 70-516 exam yesterday with a score of 860. Not as great as I would have liked but not nearly as bad as I had feared. Overall I'm pleased. For what it's worth, I followed the same route many others have followed in preparing for the exam. I started with the MCTS Self-Paced Training Kit (Exam 70-516): Accessing Data with Microsoft .NET Framework 4 which I obtained through the O'Reilly Safari Books Online. The nice thing is you also get access to the CD contents which includes a copy of the MeasureUp testing software so you can take practice exams. I imagine it isn't as up to date as what you would get if you went straight to MeasureUp or if perhaps the included copy of the software has a smaller question pool, but it was still helpful.

In any event, once I had finished going through the book and making sure to actually run through the examples and make an effort to answer the questions at the end of each chapter without looking back, I started going through the links from this blog post by ondrejsv. For each link, I would read through the linked article and then either follow relevant links in a new tab or open a new tab and search for a topic that seemed to be potentially of interest. Think of it as doing a depth-first-traversal of the MSDN articles. What I found was that there were interesting bits of information that were uncovered in the MSDN articles, especially in those containing sample code, which dealt with some topics in greater detail than the material covered in the book. In that way, the book was a good baseline for preparation but the MSDN links fleshed things out a bit more.

In conjunction with this, I continued to go through the sample tests. I kept a text file open and jotted down items that I was having difficulty with and would then spend time doing searches online for additional material covering that topic, both within and outside of MSDN. Additionally, I started creating mini-projects. Initially the projects would be very straightforward, switching back and forth between thick clients and web clients. Then I expanded to web services with various clients, splitting the EF entities into their own assembly, and so on.

I'm now considering which I should pursue next, 70-513 (covering WCF), 70-515 (Web apps) or 70-511 (Windows apps). If I'm going to shoot for MCPD, I will definitely need 513, but would need to choose which I would specialize in, Web apps or Windows apps. Still, not every project will involve WCF so perhaps for the short term I would be better off picking 515 or 511.

In any event, I've been told that 516 is considered to be more difficult than the other exams. Not having taken the others nor even studied them, I can't say. For those who have, I'm curious what your opinion is.

Filed under: development No Comments
9Dec/110

Building Buzz for your iOS App

I recently received the following email:

Hi Lynn,

I hope you are doing well and that this holiday season isn't keeping you too busy.

The reason for my e-mail is because I have a friend from College who just finished an iOS game and is looking to get some press for it. I wanted to know if there are any tips that you could share with him to help his game out there. If you could help him get in contact with any iOS websites, I'm sure he would be very grateful too.

What I responded with is more or less what I wish I had done with Centripetal and would do if I had it all to do over again. To begin with, this is premised on the notion that you are a small time developer, have virtually no reputation beyond your circle of friends and are planning to release your app with the hopes of making some money on it.

What I found is that timing is very important. When an app is first released on the app store, it will show up on lists all over the web. Ask anyone who has released a small app with little exposure what their sales looked like and they will tell you sales were highest within the first 24-48 hours of app store availability. So if you do absolutely nothing else, you will see a bump in sales and therefore in eyes on your app during that period. You want to make the most of that.

My suggestion then is to try to focus on driving your marketing and buzz building on that moment when your app first arrives on the app store. Granted, I haven't tested this since as I said this is what I would like to have done, but work with me here. 🙂

Let's assume for now that your app is not now nor has it ever been available for sale on the app store. You can make it available for review when it is done, but put the release date out into the future. This won't affect the review cycle but will keep it from being up for sale immediately after approval. Once it is approved, you can push the release date back to a current date and have control over the actual release time within a 24-48 hour window.

Next, start building buzz NOW! Don't wait for the game to be complete. Got a blog? Blog about your game. Hint at what features you're going to have. Hold a poll about which feature to include or how to implement it. Get feedback. Drum up interest. Have people anticipating the game's release. The more people get your game and at this state, the idea of the game, put in front of them the more likely they are to mention it to friends or at least share the link.

Can you create a gameplay or app usage video? If your app can be simulated on your Mac without any loss of features, then use something like Screenflick to record on your Mac. If that isn't an option, you can use a separate recording device to view a physical handheld in action. Of course that's not going to look as good, but what can you do? The point is, you want folks to see what the game is like. During development you can release videos of beta builds if you think they will be enticing. As you approach release you can snazz it up with music and such. For actual release, you definitely want to have a high quality app video available for your users to see.

You also want to have a webpage or website devoted to your app. It should make people want your game. To do this, list things people will look for in an app like yours. If it's a game, tell them how fun it will be, how many levels it has, what groundbreaking new playstyle it involves. And link to the reviews for you app.

Ah yes, reviews. I used O.A.T.S. to get a list of sites that do reviews without accepting money to create or promote your review. It's less expensive and more honest. Of course it is possible to get an honest review that you've paid for, but there is always the likelihood of a bias. If that doesn't bother you, you can of course find many sites which are willing to take your money in exchange for a review and a little extra to move yours to the top of the pile. I leave that choice to you.

To get your app into a reviewer's hands you will either have to release your app and provide promo codes, which all review sites accept but which blows the whole "control your launch date" idea out of the water, or you will need to provide them beta access through something like TestFlightApp. TestFlightApp is free and while it requires some extra fiddling on the part of the reviewer and the developer, it's pretty easy to use. I imagine a number of review sites would be willing to meet you half way and make use of this. Regardless, no reviewer is going to want to have to pay for your app in order to review it, especially if you are requesting the review. At this point, you have to wait for the review. This could take awhile. Weeks. Perhaps even a month or so. Technically there's no guarantee (unless you bought it) that you will even get a review. It's up to you how long you wait.

Once you have a satisfactory number of positive reviews you can link to, set them up on your site and in your iTunes app store description. Make the app available. Once you see it on the app store, make additional announcements about its availability anywhere you can. Ask friends to spread the word. If you're willing to spend money, you could consider advertising on sites you would expect users of your app to frequent. Advertising options vary greatly across websites. If you wish to do this, contact the webmasters of those sites directly for details.

Now, why all this fuss over timing everything to hit all at once? As I said, you get a free bump when your app is first listed on the store, especially if it is free or $0.99 because there are a number of sites and services that exist solely to scrape the iTunes App Store and list all new apps in those price ranges. It is my belief that if you can build enough buzz such that a large number of people are discussing and playing your app at the same time, it increases the odds that your app will then be mentioned to others, i.e. that it will become a hot item. And that's what this is about, increasing your odds. As I said to begin with, the premise here is that you are an indie developer with no name, no buzz and nothing to build on. If you already have established credentials, you can build on that and you'll see more success on average with the same app than if you had nothing to start with.

Of course, that's not the end of it. You should never consider your app 'finished'. Always consider things you could do to expand on it, build on it. When you release updates that provide more features or more fun or more playtime to users, it increases the perceived value and provides an opportunity for more buzz. Keep making the announcements, keep interest up.

And if your app is already on the store or if you simply disagree with this notion of opening day timing, then you can of course bust the sequence up. Release sooner, don't hold back. But the rest of it still makes sense. You still want that app use or gameplay video. You want that website. You want to build interest before you ever release anything. You want the reviews. Of course if the app is already up, you can use promo codes.

Whichever way you go, however you choose to market your app, the important thing is to give the user something they want to use and something they want to share with their friends. There's no marketing with higher quality than simple word of mouth. Hopefully, you'll be able to build your own Angry Birds. If you do follow my advice here, particularly if it's not what you were originally planning to do, I'd appreciate it if you drop me a line and let me know how it goes.